#AzureAD

Using Multiple Azure Identities Simultaneously

Profiles.png

Many Azure end users and developers have to deal with the challenges of holding multiple Microsoft and/or Azure Active Directory identities.  At a minimum, you might be like me and have an MSDN account as well as a 1 or more corporate accounts.  There may also be situations where you have development or test tenants and those use separate logins as well.  Another use case is when doing testing and having different users with different roles (i.e. Admin users, basic user, user with no access, etc.) In these situations, it can be painful (or at least annoying) to switch contexts when using those identities on the web since web browsers can only log you into one identity at a time when using sites such as portal.azure.com.  Have you ever gone to the Azure portal only to realize you last logged in with a different account and then you need to logout and back in with different credentials?  This is a common situation for me, and although it only takes 10 seconds or so to login with different credentials, the frequency this happens makes it quite a hassle.

One solution is to use different browsers for different identities (i.e. one login in Firefox, one login in Chrome).  This may work for 2 or 3 different identities, but it's not ideal since every browser will behave differently and may have different conventions.

The solution I use, which I will detail below is to utilize named profiles within Chrome, which allows for logging into as many identities as needed all at the same time.  No more logout/login hassle!

Step-by-step Guide

Here are the steps to add additional profiles to Chrome:

  1. Within Chrome, click your named profile and select Manage people
  2. Click Add Person on the dialog
  3. Type a name for the profile, select an identifying icon if desired, check or uncheck creating a desktop shortcut and then save
  4. Repeat for as many profiles you wish to utilize.  For example, I have my default which uses corporate production login, a secondary corporate development login as well as my MSDN/Microsoft login
  5. Now when you click on your profile, you have the option of opening a new window for each profile and each window maintains it's own set of cookies, browser history, etc.
  6. Here is an example of all 3 of my profiles being logged into the Azure portal all at the same time

Hope you find this useful!

Azure Role-Based Access Control, Part 1

RBAC.png

  One of the advantages of the Azure Resource Management (ARM) deployment model is being able to delegate administration, something we couldn’t do under the Azure Service Management (ASM or RDFE) deployment model (see the previous posts on subscriptions and resource groups).  By delegating administrative authority, we can keep the number of subscription admins low and grant access in accordance with the principle of Least Privilege.  This post will walk through using Azure role-based access control (RBAC) to achieve all of this.

Azure RBAC Basics

Built-In Roles

There are three general roles that apply to all resource types:

  • Owner has full access to the in-scope resources
  • Contributor has the same access to in-scope resources as Owner but cannot manage access
  • Reader can only view in-scope resources

In addition to these general roles, there are other built-in roles that are resource-specific.  Microsoft has a list of those roles, but because Azure is changing regularly it’s best to use PowerShell to query for the roles:

[powershell]

# list all of the RBAC roles in the subscription (Get-AzureRmRoleDefinition).Name

# see the actions a specific role is allowed to perform (Get-AzureRmRoleDefinition -Name '<Role Name>').Actions

# see the actions a specific role is *not* allowed to perform (Get-AzureRmRoleDefinition -Name '<Role Name>').NotActions

[/powershell]

Azure Active Directory (AD) users and groups can be assigned to any Azure RBAC role.  The Azure AD tenant to which the subscription belongs is the source tenant for users and groups.

Custom Roles

If you find that the built-in roles aren’t sufficient, you can create custom roles which will be the topic of Part 2.

Resource Hierarchy

There is a hierarchy to containers and resources:

  • The subscription is the top-level container that can house resources
  • A resource group (RG) can belong to a single subscription (and can’t span subscriptions)
  • A resource can belong to a single resource group

Any access granted to a parent container is inherited by all children.  For example, an account granted read access at the subscription level can see all resources in the subscription.  By the way, there is no concept of denying access in Azure RBAC, so be very careful and deliberate about granting wide-spread access.

ARM/New Portal vs. ASM/Classic Portal/RDFE

Back in the ASM days, to access an Azure subscription one needed to be a subscription admin (or co-admin).  Those admins are automatically granted subscription owner access in ARM.  However, accounts granted owner access in ARM are not automatically granted subscription co-admin access.  If you have resources that you need to manage that are not yet available in ARM you will still need to manage the co-admins list.  In either case the recommendation is still the same: keep the number of subscription admins (granted either through Azure RBAC or directly through subscription admins) as low as possible.

Azure RBAC in Practice

Recommendations for a resource group strategy were discussed in a previous post, so I won’t rehash that content.  What I do want to talk about is how to implement your strategy.

Creating RGs and Delegating Admin

RGs must be created by an account with subscription owner role.  Once the RG is created an RG owner must be assigned to a user who is the actual owner of the RG (owner as in someone who is responsible for the resources that the RG will contain).  Once that owner has been established additional roles can be added by the new owner.  The advice for RGs is the same as subscriptions: keep the number of owners as low as possible – not everyone who needs access to the RG needs to be an owner of the RG – use Contributor instead.

Managing Roles

To keep the administrative overhead as low as possible, use Azure AD groups to manage role membership.  Create a group and add user accounts to the group.  If you’re syncing your on-premises AD Domain Services (DS) with Azure AD create the group in AD DS and let it sync to Azure AD.  Manage these groups using your existing on-premises user and group management process.

Infrastructure RGs

Contra much of the advice published on the Internet not every service or application should get its own VNet and not every virtual machine (VM) should get its own storage account.  VNets and storage accounts for VM disks (VHDs) are infrastructure resources and need to be managed as such.

Network

Create an RG for the VNets and place all the VNets into that RG.  Create a group, name it something descriptive like Network Consumers, and grant it the Reader and Virtual Machine Contributor roles to the VNet RG.  Any user account in that group will be able to attach VMs to any of the VNets in the VNet RG.

VHD Storage Accounts

Create an RG for the VHD storage accounts and place all the VHD storage accounts into that RG.  Create a group, name it something descriptive like Storage Consumers, and grant it the Reader and Virtual Machine Contributor roles to the VHD storage account RG.  Any user account in that group will be able to use any of the storage accounts in the RG to store a VM’s VHDs.

Ruling Your Resource Groups with an Iron Fist

grim-reaper4.png

If it is not obvious by now, we deploy a lot of resources to Azure.  The rather expensive issue we encounter is that people rarely remember to clean up after themselves; it goes without saying we have encountered some staggeringly large bills.  To remedy this, we enforced a simple, yet draconian policy around persisting Resource Groups.  If your Resource Group does not possess values for our required tag set (Owner and Solution), it can be deleted at any time. At the time the tagging edict went out we had well over 1000 resource groups.  As our lab manager began to script the removal using the Azure Cmdlets we encountered a new issue, the synchronous operations of the Cmdlets just took too long.  We now use a little script we call "The Reaper" to "fire and forget".

If you read my previous post about Azure AD and Powershell, you may have noticed my predilection for doing things via the REST API.  "The Reaper" simply uses the module from that post to obtain an authorization token and uses the REST API to evaluate Resource Groups for tag compliance and delete the offenders asynchronously. There are a few caveats to note; it will only work with organizational accounts and it will not delete any resources which have a lock.

It is published on the PowerShell gallery, so if you can obtain it like so:

[powershell] #Just download it Save-Script -Name thereaper -Path "C:\myscripts" #Install the script (with the module dependency) Install-Script -Name thereaper [/powershell]

The required parameters are a PSCredential and an array of strings for the tags to inspect. Notable Switch parameters are AllowEmptyTags (only checks for presence of the required tags) and DeleteEmpty (removes Resource Groups with no Resources even if tagged properly). There is also a SubscriptionFilters parameter taking an array of Subscription id's to limit the scope, otherwise all Subscriptions your account has access to will be evaluated. If you would simply like to see what the results would be, use the WhatIf Switch. Usage is as follows:

[powershell] $Credential=New-Object PSCredential("username@tenant.com",("YourPassword"|ConvertTo-SecureString -AsPlainText -Force)) $results=.\thereaper.ps1 -Credential $Credential ` -RequiredTags "Owner","Solution" -DeleteEmpty ` -SubscriptionFilters "49f4ba3e-72ec-4621-8e9e-89d312eafd1f","554503f6-a3aa-4b7a-a5a9-641ac65bf746" [/powershell]

A standard liability waiver applies; I hold no responsibility for the Resource Groups you destroy.

Azure, Azure Active Directory, and PowerShell. The Hard Way

poshoauth.png

In my opinion, a fundamental shift for Windows IT professionals occurred with the release of Exchange 2007.  This established PowerShell as the tool for managing and configuring Microsoft enterprise products and systems going forward.  I seem to remember hearing a story at the time that a mandate was established for every enterprisey product going forward; each GUI action would have a corresponding PowerShell execution.  If anyone remembers the Exchange 2007 console, you could see that in action.  I won’t bother corroborating this story, because the end results are self-evident.  I can’t stress how important this was.  Engineers and administrators with development and advanced scripting skills were spared the further indignity of committing crimes against Win32 and COM+ across a hodgepodge of usually awful languages.  Windows administrators for whom automation and scripting only meant batch files, a clear path forward was presented.

PowerShell and Leaky Abstractions

For roughly two years now, the scope of my work has been mostly comprised of Azure integration and automation.  Azure proved to be no exception to the PowerShell new world order. I entered with wide-eyed optimism and I quickly discovered a great deal of things, usually of a more advanced nature, that could not be done in the portal and purportedly only via PowerShell. As I continue to receive product briefings, I have developed a bit of a pedantic pet-peeve.  PowerShell is always front and center in the presentations when referencing management, configuration, and automation.  However, I continue to see a general hand wave given as to the underlying technologies (e.g. WMI/CIM, REST API) and requirements.  I absolutely understand the intent, PowerShell has always been meant to provide a truly powerful environment in a manner that was highly accessible and friendly to the IT professional.  It has been a resounding success in that regard.  A general concern, I have, is that of too much abstraction.  There is a direct correlation between your frustration level and how far your understanding of what is going on is when an inevitable edge case is hit and the abstraction leaks.

Getting Back to the Point

All of that is a really long preface to the actual point of this post. I’ve never been a fan of the Azure Cmdlets for a number of reasons, most of which I don’t necessarily impugn the decisions made by Microsoft. To be honest, I think  both Switch-AzureMode (for those that remember) and the rapid release cadence that has introduced many understandably unavoidable breaking changes has really prejudiced me; as a result I tend to use the REST API almost exclusively. The fact is, modern systems and especially all of the micro-service architectures being touted are all powered by REST API. In the case of the Microsoft cloud, with only a few notable exceptions, authentication and authorization is handled via Azure Active Directory. It behooves the engineer or developer focused on Microsoft technologies to have a cursory understanding.  Azure Active Directory, Azure, and Office 365 are intrinsically linked. Every Azure and/or Office 365 Subscription is linked with an Azure AD tenant as the primary identity provider. The modern web seems to have adopted OAuth as an authorization standard and Azure AD can greatly streamline the authorization of web applications and API. The management and other API surfaces of Azure (and Azure Stack) and Office 365 have always taken advantage of this. The term you’ve likely heard thrown around is Bearer Token. That is more accurately described as an authorization header on the HTTP request containing a JWT (JSON Web Token).  My largest issue with the Azure and PowerShell automation has been the necessity to jump through hoops to simply obtain that token via PowerShell.  In 2016 a somewhat disingenuously Cmdlet named Get-AzureStackToken in the AzureRM.AzureStackAdmin module finally appeared.  I’m certain a large portion of the potential reading audience has used a tool like Fiddler, Postman, or even more recently resources.azure.com to either inspect or interact with these services.  Those who have can feel free to skip the straight to where this applies to PowerShell.

There are two types of applications you can create within Azure AD, each of with are identified with a unique Client Id and valid redirect URI(s) as the most relevant properties we’ll focus on.

Web Applications

  • Web applications in Azure Active Directory are OAuth2 confidential clients and likely the most appropriate option for modern (web) use cases.

  • Tokens are obtained on behalf of a user using the OAuth2 authorization grant flow. An authorization code or id token will be supplied to the specified redirect URI.

  • If needed, client credentials (a rolling secret key) can be used to obtain tokens on behalf of the user or on it’s own from the web application itself.

Native Applications

  • Native applications in Azure Active Directory are OAuth2 public clients (e.g. an application on a desktop or mobile device).

  • These applications can obtain a token directly (with managed organizational accounts) or use the authorization grant flow, but application level permissions are not applicable.

Getting to the PowerShell

I will focus primarily on the Native application type as it is most relevant to PowerShell. Most of the content will use Cmdlets from a module that will be available with this post.   The module is heavily derived/inspired by the ADAL libraries, has no external dependencies and accept a friendly PSCredential (with the appropriate rights) for any user authentication.  The Azure Cmdlets use a Native application with a Client Id of 1950a258-227b-4e31-a9cf-717495945fc2 and a redirect URI of urn:ietf:wg:oauth:2.0:oob (the prescribed default for native applications).   We’ll use this for our first attempt at obtaining a token for use against Azure Resource Manager or the legacy Service Management API.  A peculiar detail of Azure management is that this one of the scenarios a token is fungible for disparate endpoints. I always use https://management.core.windows.net as my audience regardless of whether I will be working with ARM or SM.  A token obtained from that audience will work the same as one from https://management.azure.com .

If all you would like is a snippet to obtain a token using the Azure, I’ll offer you a chance to bail out now:


$Resource='https://management.core.windows.net'
$PoshClientId="1950a258-227b-4e31-a9cf-717495945fc2"
$TenantId="yourdomain.com"
$UserName="username@$TenantId"
$Password="asecurepassword"|ConvertTo-SecureString -AsPlainText -Force
$Credential=New-Object pscredential($UserName,$Password)
Get-AzureStackToken -Resource $Resource -AadTenantId $TenantId -ClientId $PoshClientId -Credential $Credential -Authority "https://login.microsoftonline.com/$TenantId" 

A good deal of the functionality around provisioning applications and service principals has come to the Azure Cmdlets.  You can now create applications, service principals from the applications, and role assignments to the service principals. To create an application, in this case one that would own a subscription, you would write something like this:


$ApplicationSecret="ASuperSecretPassword!"
$TenantId='e05b8b95-8c85-49af-9867-f8ac0a257778'
$SubscriptionId='bc3661fe-08f5-4b87-8529-9190f94c163e'
$AppDisplayName='The Subscription Owning Web App'
$HomePage='https://azurefieldnotes.com'
$IdentifierUris=@('https://whereeveryouwant.com')
$NewWebApp=New-AzureRmADApplication -DisplayName $AppDisplayName -HomePage $HomePage `
    -IdentifierUris $IdentifierUris -StartDate (Get-Date) -EndDate (Get-Date).AddYears(1) `
    -Password $ApplicationSecret
$WebAppServicePrincipal=New-AzureRmADServicePrincipal -ApplicationId $NewWebApp.ApplicationId
$NewRoleAssignment=New-AzureRmRoleAssignment -ObjectId $NewWebApp.Id -RoleDefinitionName 'owner' -Scope "/subscriptions/$SubscriptionId"
$ServicePrincipalCred=New-Object PScredential($NewWebApp.ApplicationId,($ApplicationSecret|ConvertTo-SecureString -AsPlainText -Force))
Add-AzureRmAccount -Credential $ServicePrincipalCred -TenantId $TenantId -ServicePrincipal 

For those that stuck around, let’s take a look at obtaining JWT(s), inspecting them, and putting them to use.

I added a method for decoding the tokens, so we will have a look at the access token.  A JWT is comprised of a header, payload, and signature.  I will leave explaining the claims within the payload to identity experts.

Now that we have a token, let's use it for something useful, in this case we will ask Azure (ARM) for our associated subscriptions.

Examining the OAuth2 Flow

If you are not interested in what is going on behind the scenes feel free to skip ahead.  Each application exposes a standard set of endpoints and I will not discuss the v2.0 endpoint as I do not have enough experience using it.  There are two endpoints in particular to make note of, https://login.microsoftonline.com/{tenantid}/oauth2/authorize and https://login.microsoftonline.com/{tenantid}/oauth2/token, where {tenantid} represents the tenant id (guid or domain name) e.g. yourcompany.com or common for multi-tenant applications.  Azure AD obviously supports federation and the directing traffic to the appropriate authorization endpoint is guided by a user realm detection API of various versions at https://login.microsoftonline.com/common/UserRealm.  If we inspect the result for a fully managed Azure AD account we see general tenant detail.

If we take a look at a federated user we will see a little difference, the AuthURL property.

userrealm federated

This show us the location of our federated authentication endpoint. The token will actually be requested via a SAML user assertion that is received from an STS, in this case ADFS.

The OAuth specification uses the request parameter collection for token and authorization code responses. A username and password combination can be used to directly request a token in the fully managed scenario public client scenario.

A POST request can go directly to the Token endpoint with the following query parameters:

client_id

The Application Id

resource

The Resource URI to access

grant_type

password

username

The username

password

The password

The ADFS/WSTrust will entail sending a SOAP request to the WSTrust endpoint to authenticate and use that response to create the assertion that is exchanged for an access token.  Through user realm detection we can find the ADFS username/password endpoint.  A SOAP envelope can be sent to  endpoint to receive a security token response, containing the assertions needed.

A POST request is sent to the Username/Password endpoint for ADFS with the following envelope with noteable values encased in {}:

<s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope' 
    xmlns:a='http://www.w3.org/2005/08/addressing' 
    xmlns:u='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'>
    <s:Header>
        <a:Action s:mustUnderstand='1'>http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
        <a:messageID>urn:uuid:{Unique Identifier for the Request}</a:messageID>
        <a:ReplyTo>
            <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
        </a:ReplyTo>        <!-- The Username Password WSTrust Endpoint -->
        <a:To s:mustUnderstand='1'>{Username/Password Uri}</a:To>
        <o:Security s:mustUnderstand='1' 
            xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'>            <!-- The token length requested -->
            <u:Timestamp u:Id='_0'>
                <u:Created>{Token Start Time}</u:Created>
                <u:Expires>{Token Expiry Time}</u:Expires>
            </u:Timestamp>            <!-- The username and password used -->
            <o:UsernameToken u:Id='uuid-{Unique Identifier for the Request}'>
                <o:Username>{UserName to Authenticate}</o:Username>
                <o:Password>{Password to Authenticate}</o:Password>
            </o:UsernameToken>
        </o:Security>
    </s:Header>
    <s:Body>
        <trust:RequestSecurityToken xmlns:trust='http://docs.oasis-open.org/ws-sx/ws-trust/200512'>
            <wsp:AppliesTo xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'>
                <a:EndpointReference>
                    <a:Address>urn:federation:MicrosoftOnline</a:Address>
                </a:EndpointReference>
            </wsp:AppliesTo>
            <trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
            <trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
        </trust:RequestSecurityToken>
    </s:Body>
</s:Envelope>

The token response is inspected for SAML assertion types (urn:oasis:names:tc:SAML:1.0:assertion or urn:oasis:names:tc:SAML:2.0:assertion) to find the matching token used for the OAuth token request.

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" 
    xmlns:a="http://www.w3.org/2005/08/addressing" 
    xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <s:Header>
        <a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTRC/IssueFinal</a:Action>
        <o:Security s:mustUnderstand="1" 
            xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <u:Timestamp u:Id="_0">
                <u:Created>2016-01-03T01:34:41.640Z</u:Created>
                <u:Expires>2016-01-03T01:39:41.640Z</u:Expires>
            </u:Timestamp>
        </o:Security>
    </s:Header>
    <s:Body>
        <trust:RequestSecurityTokenResponseCollection xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">            <!-- Our Desired Token Response -->
            <trust:RequestSecurityTokenResponse>
                <trust:Lifetime>
                    <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2016-01-03T01:34:41.622Z</wsu:Created>
                    <wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2016-01-03T02:34:41.622Z</wsu:Expires>
                </trust:Lifetime>
                <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
                    <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
                        <wsa:Address>urn:federation:MicrosoftOnline</wsa:Address>
                    </wsa:EndpointReference>
                </wsp:AppliesTo>
                <trust:RequestedSecurityToken>                    <!-- The Assertion -->
                    <saml:Assertion MajorVersion="1" MinorVersion="1" AssertionID="_e3b09f2a-8b57-4350-b1e1-20a8f07b3d3b" Issuer="http://adfs.howtopimpacloud.com/adfs/services/trust" IssueInstant="2016-08-03T01:34:41.640Z" 
                        xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion">
                        <saml:Conditions NotBefore="2016-01-03T01:34:41.622Z" NotOnOrAfter="2016-01-03T02:34:41.622Z">
                            <saml:AudienceRestrictionCondition>
                                <saml:Audience>urn:federation:MicrosoftOnline</saml:Audience>
                            </saml:AudienceRestrictionCondition>
                        </saml:Conditions>
                        <saml:AttributeStatement>
                            <saml:Subject>
                                <saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">130WEAH65kG8zfGrZFNlBQ==</saml:NameIdentifier>
                                <saml:SubjectConfirmation>
                                    <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
                                </saml:SubjectConfirmation>
                            </saml:Subject>
                            <saml:Attribute AttributeName="UPN" AttributeNamespace="http://schemas.xmlsoap.org/claims">
                                <saml:AttributeValue>chris@howtopimpacloud.com</saml:AttributeValue>
                            </saml:Attribute>
                            <saml:Attribute AttributeName="ImmutableID" AttributeNamespace="http://schemas.microsoft.com/LiveID/Federation/2008/05">
                                <saml:AttributeValue>130WEAH65kG8zfGrZEFlBQ==</saml:AttributeValue>
                            </saml:Attribute>
                        </saml:AttributeStatement>
                        <saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:password" AuthenticationInstant="2016-08-03T01:34:41.607Z">
                            <saml:Subject>
                                <saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">130WEAH65kG8sfGrZENlBQ==</saml:NameIdentifier>
                                <saml:SubjectConfirmation>
                                    <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
                                </saml:SubjectConfirmation>
                            </saml:Subject>
                        </saml:AuthenticationStatement>
                        <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                            <ds:SignedInfo>
                                <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                                <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
                                <ds:Reference URI="#_e3b09f2a-8b57-4350-b1e1-20a8f07b3d3b">
                                    <ds:Transforms>
                                        <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                                        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                                    </ds:Transforms>
                                    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                                    <ds:DigestValue>itvzbQhlzA8CIZsMneHVR15FJlY=</ds:DigestValue>
                                </ds:Reference>
                            </ds:SignedInfo>
                            <ds:SignatureValue>gBCGUmhQrJxVpCxVsy2L1qh1kMklVVMoILvYJ5a8NOlezNUx3JNlEP7wZ389uxumP3sL7waKYfNUyVjmEpPkpqxdxrxVu5h1BDBK9WqzOICnFkt6JPx42+cyAhj3T7Nudeg8CP5A9ewRCLZu2jVd/JEHXQ8TvELH56oD5RUldzm0seb8ruxbaMKDjYFuE7X9U5sCMMuglU3WZDC3v6aqmUxpSd9Kelhddleu33XEBv7CQNw84JCud3B+CC7dUwtGxwv11Mk/P0t1fGbfs+I6aSMTecKq9YmscqP9tB8ZouD42jhjhYysOQSdulStmUi6gVzQz+c2l2taa5Amd+JCPg==</ds:SignatureValue>
                            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                                <X509Data>
                                    <X509Certificate>MIIC4DCDAcigAwIBAgIQaYQ6QyYqcrBBmOHSGy0E1DANBgkqhkiG9w0BAQsFADArMSkwJwYDVQQDEyBBREZTIFNpZ25pbmcgLSBhZGZzLmNpLmF2YWhjLmNvbTAgFw0xNjA2MDQwNjA4MDdaGA8yMTE2MDUxMTA2MDgwN1owKzEpMCcGA1UEAxMgQURGUyBTaWduaW5nIC0gYWRmcy5jaS5hdmFoYy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDH9J6/oWYAR8Y98QnacNouKyIBdtZbosEz0HyJVyrxVqKq2AsPvCEO3WFm9Gmt/xQN9PuLidZpgICAe8Ukuv4h/NldgmgtD64mObFNuEM5pzAPRXUv6FWlVE4fnUpIiD1gC0bbQ7Tzv/cVgfUChCDpFu3ePDTs/tv07ee22jXtoyT3N7tsbIX47xBMKgF9ItN9Oyqi0JyQHZghVQ1ebNOMH3/zNdl0WcZ+Pl+osD3iufoH6H+qC9XY09B5YOWy8fJoqf+HFeSWZCHH5vJJfsPTsSilvLHCpMGlrMFaTBKqmv+m9Z3FtbzOcnKHS5PJVAymqLctkH+HbFzaDblaSRhhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFB0E2Cj+O24aPM61JsCXLIAB28q4h4qLxMwV+ypYjFxxcQ5GzgqaPJ7BARCnW1gm3PyvNfUut9RYrT9wTJlBVY9WDBoX33jsS87riMj+JONXJ7lG/zAozxs0xIiW+PNlFdOt7xyvYstrFgPJS1E05jhiZ2PR8MS20uSlMNkVPinpz4seyyMQeM+1GbpbDE1EwwtEVKgatJN7t6nAn9mw8cHIk1et7CYOGeWCnMA9EljzNiD8wEwsG51aKfuvGrPK8Q8N/G89SPgstpe0Te5+EtWT6latXfpCwdNWxvinH49SKKa25l1VoLLNwKiQF6vK1Iw0F7dP7QkO5YdE7/MTDU=</X509Certificate>
                                </X509Data>
                            </KeyInfo>
                        </ds:Signature>
                    </saml:Assertion>
                </trust:RequestedSecurityToken>
                <trust:RequestedAttachedReference>
                    <o:SecurityTokenReference k:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1" 
                        xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
                        xmlns:k="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd">
                        <o:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID">_e3b09f2a-8b57-4350-b1e1-20a8f07b3d3b</o:KeyIdentifier>
                    </o:SecurityTokenReference>
                </trust:RequestedAttachedReference>
                <trust:RequestedUnattachedReference>
                    <o:SecurityTokenReference k:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1" 
                        xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
                        xmlns:k="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd">
                        <o:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID">_e3b09f2a-8b57-4350-b1e1-20a8f07b3d3b</o:KeyIdentifier>
                    </o:SecurityTokenReference>
                </trust:RequestedUnattachedReference>
                <trust:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</trust:TokenType>
                <trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
                <trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
            </trust:RequestSecurityTokenResponse>
        </trust:RequestSecurityTokenResponseCollection>
    </s:Body>
</s:Envelope>

A POST request is sent to the Token endpoint with the following query parameters:

client_id

The Application Id

resource

The Resource URI to access

assertion

The base64 encoded SAML token

grant_type

urn:ietf:params:oauth:grant-type:saml1_1-bearer urn:ietf:params:oauth:grant-type:saml2-bearer

scope

openid

A GET request is sent to the Authorize endpoint with some similar query parameters:

client_id

The Application Id

redirect_uri

The location within the application to handle the authorization code

response_type

code

prompt

login consent admin_consent

scope

optional scope for access (app uri or openid scope)

The endpoint should redirect you to the appropriate login screen via user realm detection.  Once the user login is completed, the code is added to the redirect address as either query parameters (default) or a form POST.  Once the code is retrieved it can be exchanged for a token. A POST request is sent to the Token endpoint as demonstrated before with some slightly different parameters:

client_id

The Application Id

resource

The Resource URI to access

code

The authorization code

grant_type

authorization_code

scope

previous scope

client_secret

required if confidential client

Tying it All Together

To try to show some value for your reading time, lets explore how this can be used as the solutions you support and deploy become more tightly integrated with the Microsoft cloud.  We'll start by creating a new Native application in the legacy portal.

appnative1
appnative2

I used https://itdoesnotmatter here, but you might as well follow the guidance of using urn:ietf:wg:oauth:2.0:oob.  We will now grant permissions to Azure Active Directory and Azure Service Management (for ARM too).

ADPermissions
ADServiceMgmt

I will avoid discussing configuring the application to be multi-tenant as the processes I outline are identical, it is simply a matter of the targeted tenant.  You should end up with something looking like this.

Native

Let's now try to go get a token for our new application and put it to use.  This should look exactly the same as retrieving the previous token.


$AuthCode=Approve-AzureADApplication -ClientId $NewClientId -RedirectUri 'https://itdoesnotmatter/' -TenantId sendthewolf.com -AdminConsent

nativefirstattempt

Epic failure!  Unfortunately we run into a common annoyance, the application must be consented to interactively.  I do not know of any tooling that exists to make this easy.  I added a function to make this a little easier and it supports a switch of AdminConsent to approve the application for all users within the tenant.  And step through the consent process to receive an authorization code.


$AuthCode=Approve-AzureADApplication -ClientId $NewClientId -RedirectUri 'https://itdoesnotmatter/' -TenantId sendthewolf.com -AdminConsent

Approve
Approve App

Once the authorization code is obtained it can be exchanged for a token, for which I provided another function.  That token can now be used in the exact same manner as the Azure Cmdlet application.


$TokenResult=Get-AzureADAccessTokenFromCode 'https://management.core.windows.net/' -ClientId $NewClientId -RedirectUri 'https://itdoesnotmatter/' -TenantId sendthewolf.com -AuthorizationCode $AuthCode

Authorize2

If you wanted to handle some Azure Active Directory objects, we can target a different audience, and execute actions appropriate to the account's privilege level.   In the following example we will create a new user.


$GraphBaseUri="https://graph.windows.net/"
$GraphUriBuilder=New-Object System.UriBuilder($GraphBaseUri)
$GraphUriBuilder.Path="$TenantId/users"
$GraphUriBuilder.Query="api-version=1.6"
$NewUserJSON=@"
{
    "accountEnabled": true, 
    "displayName": "Johnny Law", 
    "mailNickName" : "thelaw", 
    "passwordProfile": { 
        "password": "Password1234!", 
        "forceChangePasswordNextLogin": false 
    }, 
    "userPrincipalName": "johhny.law@$TenantId" 
}
"@
$AuthResult=Get-AzureADUserToken -Resource $GraphBaseUri -ClientId $NewClientId -Credential $Credential -TenantId $TenantId
$AuthHeaders=@{Authorization="Bearer $($AuthResult.access_token)"}
$NewUser=Invoke-RestMethod -Uri $GraphUriBuilder.Uri -Method Post -Headers $AuthHeaders -Body $NewUserJSON -ContentType "application/json"

If we want to continue the “fun” with Office 365 we can apply the exact sample approach with the Office 365 Sharepoint Online application permissions.  In the interest of moving along and with no regard for constraining access, we will configure the permissions in the following manner.

sharepoint

We’ll now do some querying of the Office 365 SharePoint video API with some more script.


$SharepointUri='https://yourdomain.sharepoint.com/'
$SpUriBuilder=New-Object System.UriBuilder($SharepointUri)
$SpUriBuilder.Path="_api/VideoService.Discover"
$AuthResult=Get-AzureADUserToken -Resource $SharepointUri -ClientId $NewClientId -Credential $Credential
$Headers=@{Authorization="Bearer $($AuthResult.access_token)";Accept="application/json";}
$VideoDisco=Invoke-RestMethod -Uri $SpUriBuilder.Uri -Headers $Headers $VideoDisco|Format-List
$VideoChannelId="306488ae-5562-4d3e-a19f-fdb367928b96"
$VideoPortalUrl=$VideoDisco.VideoPortalUrl
$ChannelUrlBuilder=New-Object System.UriBuilder($VideoPortalUrl)
$ChannelUrlBuilder.Path+="/_api/VideoService/Channels"
$ChannelOData=Invoke-RestMethod -Uri $ChannelUrlBuilder.Uri -Headers $Headers
$ChannelRoot=$ChannelUrlBuilder.Path
foreach ($Channel in $ChannelOData.Value)
{  
    $VideoUriBuilder=New-Object System.UriBuilder($Channel.'odata.id')
    $VideoUriBuilder.Path+="/Videos"
    Invoke-RestMethod -Uri $VideoUriBuilder.Uri -Headers $Headers|Select-Object -ExpandProperty value
}

We should see some output that looks like this:

spvideos

I’ve had Enough! Please Just Show me the Code.

For those who have endured or even skipped straight here, I present the following module for any use your dare apply.  The standard liability waiver applies and it is presented primarily for educational purposes.  It came from a need to access the assortment of Microsoft cloud API in environments where we could not always ensure the plethora of correct Cmdlets are installed.  Initially, being a .Net guy, I just wrapped standard use cases around ADAL .Net.  I really wanted to make sure that I really understood OAuth and OpenId Connect authorization flows as is relates to Azure Active Directory.  The entire theme of this lengthy tome is to emphasize the importance of having a relatively advanced understanding of these concepts.  Regardless of your milieu, if it has a significant Microsoft component, the demand to both integrate and support the integration(s) of numerous offerings will only grow larger.  The module is primarily targeted at the Native Client application type, however there is support for the client secret and implicit authorization flows.  There are also a few utility methods that are exposed as they may have some diagnostic use or otherwise.  The module exposes the following methods all of which support Get-Help:

  • Approve-AzureADApplication

    • Approves an Azure AD Application Interactively and returns the Authorization Code

    • ConvertFrom-EncodedJWT

      • Converts an encoded JWT to an object representation

      • Get-AzureADAccessTokenFromCode

        • Retrieves an access token from a consent authorization code

        • Get-AzureADClientToken

          • Retrieves an access token as a an OAuth confidential client

          • Get-AzureADUserToken

            • Retrieves an access token as a an OAuth public client

            • Get-AzureADImplicitFlowToken

              • Retrieves an access token interactively for a web application with OAuth implicit flow enabled

              • Get-AzureADOpenIdConfiguration

                • Retrieves the OpenId connect configuration for the specified application

                • Get-AzureADUserRealm

                  • Retrieves a the aggregate user realm data for the specified user principal name(s

                  • Get-WSTrustUserRealmDetails

                    • Retrieves the WSFederation details for a given user prinicpal name

Get it here: Azure AD Module

I hope you find it useful and remember not to fear doing things the hard way every so often.