Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Making your Azure Active Directory application Multi-tenanted

So far in my previous posts I’ve discussed signing into an application using Azure Active Directory (Azure AD) using a basic application registration in Azure AD. Last post we added some additional permissions that required administrator consent. However, up until now, only users in the same directory (aka tenant) that the application is registered in, can sign in. In the case of the sample application I’ve been working with, the application is registered to the nicksdemodir.onmicrosoft.com tenant, so only users belonging to that tenant can sign in eg [email protected]. If I attempt to sign in with an account from a different tenant, I run into a few issues, and depending on what type of account I sign in with, the error that is displayed varies.

I’ll start by signing in with a regular user account that belongs to a different tenant (in this case btro365dev.onmicrosoft.com). When I attempt to sign in with this account, everything seems to go well – I’m prompted to sign in; I successfully sign in; I’m returned to the application where the code is exchanged for an access token. However, when I attempt to use this access token I get a rather cryptic error about an “Unsupported token” eg:

{"odata.error":{"code":"Request_BadRequest","message":{"lang":"en","value":"Unsupported token. Unable to initialize the authorization context."},"requestId":"86481ea2-79bd-461b-93ad-4f649286617a","date":"2017-01-25T21:28:14"}}

This is actually less cryptic than it seems – essentially it’s saying that I’m attempting to present a token that the API can’t process. If you were to open the access token in https://jwt.io, you’d see that the token has been issued by the nicksdemodir.onmicrosoft.com tenant (actually you’d see the STS url that correlates to this tenant) for an account that doesn’t exist in that tenant (eg [email protected]). Whilst this is a legitimate access token, when you present it to the Graph API, it attempts to retrieve information about the user in the issuing tenant, which of course fails, since the user doesn’t exist there.

Ok, let’s see what happens if I attempt to launch the admin consent prompt. In this case, after I sign in (now using [email protected] which is a global administrator) I get a more useful error message saying “AADSTS50020: User account … does not exist in tenant”.

image

The reason this error message is useful is that it prompts me to think about what I’m attempting to do – I’ve been prompting the user to sign into the nicksdemodir.onmicrosoft.com tenant, which is fine if I’m a user that belongs to that tenant but since I’m attempting to use a different user, this is clearly not correct. So, the first step in making my application multi-tenanted is to change the authorization url that I’m directing the user to in order to sign in, to one that is more generic and will allow signing in by users from any tenant. This involves exchanging the “nicksdemodir.onmicrosoft.com” with “common” in the url – the following code shows how I adapted the code in my sample application to support urls that are single tenanted (ie AuthorizationUrl and AdminConsentUrl) as well as the multi-tenanted equivalent (ie MultiTenantAuthorizationUrl and MultiTenantAdminConsentUrl).

private string BaseAuthorizationUrl =>
    "
https://login.microsoftonline.com/{0}/oauth2/authorize?" +
    "client_id=40dba662-4c53-4154-a5cf-976473306060&" +
    "response_type=code&" +
    "redirect_uri=sample://callback&" +
    "nonce=1234&" +
    "resource=
https://graph.windows.net";

private string AuthorizationUrl => string.Format(BaseAuthorizationUrl, "nicksdemodir.onmicrosoft.com");

private string AdminConsentUrl => $"{AuthorizationUrl}&prompt=admin_consent";
private string MultiTenantAuthorizationUrl => string.Format(BaseAuthorizationUrl, "common");
private string MultiTenantAdminConsentUrl => $"{MultiTenantAuthorizationUrl}&prompt=admin_consent";

For example:

Authorization url: https://login.microsoftonline.com/nicksdemodir.onmicrosoft.com/oauth2/authorize?client_id=40dba662-4c53-4154-a5cf-976473306060&response_type=code&redirect_uri=sample://callback&nonce=1234&resource=https://graph.windows.net

Multi-tenant authorization url: https://login.microsoftonline.com/common/oauth2/authorize?client_id=40dba662-4c53-4154-a5cf-976473306060&response_type=code&redirect_uri=sample://callback&nonce=1234&resource=https://graph.windows.net

Attempting to sign in using the multi-tenant authorization url with an account that doesn’t belong to the nicksdemodir.onmicrosoft.com tenat now yields the following error:

image

This error seems to be correct, considering the application registration exists in the nicksdemodir.onmicrosoft.com tenant. Unfortunately it’s not very clear what you need to do in order to fix this issue – you can’t add the application to the btro365dev.onmicrosoft.com tenant, and even if you could, you wouldn’t want to have to manually do that for every tenant that you want to support. Luckily, there is a mechanism that allows Azure AD to essentially add the application to new tenants on an as required basis (similar to how applications are added to a users list of applications at https://myapps.microsoft.com as they consent to use of the application). In order for Azure AD to do this, the application registration has to be configured to support multiple tenants. Click on the Manifest button for the application registration in Azure AD – the property “availableToOtherTenants” should be set to true (default is false).

image

Now when we attempt to sign in (again with a non-global administrator user) we see a familiar error saying that the calling principal (ie the signed in user) doesn’t have permissions.

image

We know how to fix this from my previous post, the only difference is that we need to direct the user to the multi-tenant admin consent url eg

https://login.microsoftonline.com/common/oauth2/authorize?client_id=40dba662-4c53-4154-a5cf-976473306060&response_type=code&redirect_uri=sample://callback&nonce=1234&resource=https://graph.windows.net&prompt=admin_consent

The global administrator, after signing in, will again see the admin consent prompt – it’s worth pointing out here that it lists both the directory the user is signed into (ie BTR Office Dev – btro365dev.onmicrosoft.com) and the tenant that the application is published (ie registered) in (ie DemoDirectory – nicksdemodir.onmicrosoft.com).

image

Again, consenting will enable all users in the foreign tenant (ie btro365dev.onmicrosoft.com) to then access the application via the multi-tenant authorization url.

There is one last adjustment that has to be made, and that’s to the Token url. When exchanging the code for an access token, it’s important that the token url is also adjusted to be either single or multi-tenant. Previously the url included the tenant that the application was registered to ie nicksdemodir.onmicrosoft.com. This needs to be changed in the multi-tenant scenario to use “common”. In order to allow authorization to occur for both single and multi-tenant scenarios within the application, I needed a way to dynamically control the token url based on whether the user signed in via the single tenant or multi-tenant authorization url. Currently, all we get back when the user has signed in is a code which we need to exchange for an access token. Luckily, we can pass an additional parameter, “state,” into the authorization url, which will get passed back to the application along with the code – we can use this to determine which authorization url was used. I’m only going to adjust the multi-tenant authorization url as the application will treat the lack of state parameter to mean that the user was directed to the single tenant authorization url.

private string MultiTenantAuthorizationUrl => string.Format(BaseAuthorizationUrl, "common") + "&state=multi";
private string MultiTenantAdminConsentUrl => $"{MultiTenantAuthorizationUrl}&prompt=admin_consent";

Now, the token url is updated based on the state parameter value:

private string BaseTokenUrl => "https://login.microsoftonline.com/{0}/oauth2/token";
private string TokenUrl(bool isMulti)
{
    return string.Format(BaseTokenUrl, isMulti ? "common" : "nicksdemodir.onmicrosoft.com");
}

var isMulti = uri?
                    .Split('?').Skip(1).FirstOrDefault()?
                    .Split('&').Select(q => q.Split('='))
                    .Where(x => x.Length == 2 && x[0] == "state")
                    .Select(x => x[1])
                    .FirstOrDefault() == "multi";

var tokenUrl = TokenUrl(isMulti);

And that’s it – a user can sign in from any tenant (assuming the global administrator has signed in an consented) and retrieve information from the Azure Graph API.

Verifying Azure Active Directory JWT Tokens

When working with OAuth and Open ID Connect, there are times when you’ll want to inspect the contents of id, access or refresh tokens. The website https://jwt.io is useful as you can drop in the token in the pane on the left, and the site dynamically decodes the header, body and signature for the JWT.

image

Unfortunately by itself the signature on the JWT can’t be verified as the website doesn’t know what key to use to validate the signature. The header of the JWT does provide information about the algorithm used (ie RS256) and the id of the key used but this by itself isn’t enough to locate the key to be used.

image

As RS256 is a public/private key algorithm, there is a private key, which the issuer holds, and a public key which is available to anyone to access. The former is used to generate the signature for a JWT; the later can then be used to validate the signature. To find the public key to use to validate the signature I’ll start with the OpenID Connect configuration document, which is available for any tenant at:

https://login.microsoftonline.com/{tenantId}/.well-known/openid-configuration

eg https://login.microsoftonline.com/nicksdemodir.onmicrosoft.com/.well-known/openid-configuration

The returned configuration document contains an attribute, jwks_uri, which points at https://login.microsoftonline.com/common/discovery/keys

image

Loading the jwks_uri returns another JSON document which lists a number of keys. Now we can use the kid from the header of the JWT to identify which key to use, in this case the first key in the list.

image

Attempting to simply copy the x5c value from the list of keys into the Public Key or Certificate box on the jwt.io website will still not verify the signature of the JWT. In order to verify the signature, wrap the key in BEGIN and END CERTIFICATE markers as follows:

-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIQEsuEXXy6BbJCK3bMU6GZ/TANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE2MTEyNjAwMDAwMFoXDTE4MTEyNzAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKd6Sq5aJ/zYB8AbWpQWNn+zcnadhcMYezFvPm85NH4VQohTm+FMo3IIJl6JASPSK13m9er3jgPXZuDkdrEDHsF+QMEvqmffS2wHh3tKzasw4U0jRTYB0HSCbmnw9HpUnv/UJ0X/athO2GRmL+KA2eSGmb4+5oOQCQ+qbaRXic/RkAOLIw1z63kRneLwduQMsFNJ8FZbWkQFj3TtF5SL13P2s/0PnrqwGD59zcbDu9oHOtciu0h++YhF5CWdWEIgafcZk9m+8eY12BKamvPdBnyfpz6GVTenJQe2M+AGz5RSNshvI976VUbBiaIeNzvzaG91m62kFWLRqE3igq6D02ECAwEAAaMhMB8wHQYDVR0OBBYEFAgoZ9HLgFxH2VFGP6PGc4nFizD2MA0GCSqGSIb3DQEBCwUAA4IBAQBSFXalwSJP/jihg04oJUMV2MTbuWtuFhdrdXiIye+UNc/RX02Q9rxd46BfGeKEBflUgNfEHgyEiWTSLAOSDK70vu+ceCVQCGIQPjnGyYOpm80qAj/DNWZujVcSTTV3KZjMFsBVP7miQowfJQ58u9h8yuJHNhPpB2vOFmNhm4uZq3ve529Xt51HdtQGG9+Z9n1DhObqzkbz8xEFjA+KdfcRsZXa14ZkpAOe35VgyY0f8x34Y0LPfibWcNpfp0AhxKzyqT1GRRlKTjiBA6WNJIJIEeqh/nfOnwM0UQKRnt+2qeV3u00a5lrvJtEy7nq+s7xYtpVAsCvn5T0U1/8IHkxt
-----END CERTIFICATE-----

Entering the wrapped key into the Public Key or Certificate box on the jwt.io website will successfully verify the signature of the JWT.

image

Authorizing Access to Resources using Azure Active Directory

In my previous post I discussed authenticating a user using Azure Active Directory (Azure AD), returning an id_token that can be used to identify the user that has signed in. However, this token isn’t an access token and as such can’t be presented in order to access remote services. In this post I’m going to show how you can request an access token that can be presented in the Authorization header when calling a service. The workflow is very similar to the workflow to retrieve the id_token:

- User attempts to sign into an application

- Application launches the Authorize URL in an external browser (includes “resource” parameter in Authorize URL)

- User is directed to a Microsoft URL where they are prompted to sign in

- User signs in

- User is prompted to consent that the application can access the specified resource

- After sign in, the User is redirected back to the application via a custom protocol

- Application receives an authorization code

- Application performs a POST request to the Token URL in order to exchange authorization code for an access token

- Application receives the access token

- Application makes call to remote service, attaching the access token in the Authorization header

To demonstrate this workflow I’m going to adapt the sample application I created in my previous post in order to access the Azure Graph API. The sample application is already configured with access to the Azure Graph API – this is done by default for all application registrations. In order to request access to a resource, you need to know the url of the resource you want to access. In this case the url for the Azure Graph API is https://graph.windows.net.

In addition to including the resource url in the authorization url, the other change I need to make is to switch the response type from id_token to code. The updated authorization url is:

var  authorizationUrl=
    "https://login.microsoftonline.com/nicksdemodir.onmicrosoft.com/oauth2/authorize?" +
    "client_id=40dba662-4c53-4154-a5cf-976473306060&" +
    "response_type=code&" +
    "redirect_uri=sample://callback&" +
    "nonce=1234&" +
    "resource=https://graph.windows.net";

Launching this url in the external browser will again prompt the user to sign in (unless they have previously signed in for this application, such as if you followed my previous post) but rather than immediately being redirected back to the application, the user will see a consent prompt – you’ll note that similar to the sign in prompt, the name of the application specified in Azure AD is used when requesting permissions.

image

The other thing to note is that once you’ve consented permissions for the application, you won’t be prompted again – Azure AD remembers that you’ve granted permissions. Permissions can be revoked by going to https://myapps.microsoft.com, selecting the application and clicking Remove.

image

The same “Remove” option is available if you turn on the new look (it’s the same url to get there – https://myapps.microsoft.com)

image

After completing the sign in and consent workflow, the user is navigated back to the application using the custom protocol. This time, instead of an id_token, the application receives a code as part of the url:

sample://callback/?code=AQABAAAAAADRN…….L7YiQ7PIAA&session_state=ffffd83b-3820-489e-9f35-70e97d58fd04

Unlike the id_token, and as you’ll see soon, the access_token, the code is not a jwt token that you can interrogate for information about the signed in user. Instead the next step in the process is to do the code-token exchange to retrieve the required access token. This involves doing a POST request to the Token URL, passing parameters in the body of the request.

Token URL: https://login.microsoftonline.com/{tenantId}/oauth2/token

eg https://login.microsoftonline.com/nicksdemodir.onmicrosoft.com/oauth2/token

The POST body needs to contain the following values:

var postBody = new Dictionary<string, string>
{
    {"grant_type", "authorization_code"},
    {"client_id", "40dba662-4c53-4154-a5cf-976473306060"},
    {"redirect_uri", "sample://callback"},
    {"resource", "https://graph.windows.net"},
    {"code", code}
};

40dba662-4c53-4154-a5cf-976473306060 – This is the client id (aka application id) of the application registration in Azure AD

sample://callback – This is the redirect uri specified in the application registration

https://graph.windows.net – This is the resource that you’re requesting an access token for. Make sure this is the URL for the Azure Graph API, not to be confused with the Microsoft Graph API (https://graph.microsoft.com)

code – This is the actual code that is returned from the sign in process (ie don’t use the word “code”)

The resulting code for parsing the code from the application redirect, and then exchange for an access token is as follows:

protected override async void OnActivated(IActivatedEventArgs args)
{
    base.OnActivated(args);
    if (args.Kind == ActivationKind.Protocol)
    {
        var eventArgs = args as ProtocolActivatedEventArgs;

        var uri = eventArgs.Uri.AbsoluteUri;
        var code = uri?
                    .Split('?').Skip(1).FirstOrDefault()?
                    .Split('&').Select(q => q.Split('='))
                            .Where(x=>x.Length==2 && x[0]=="code")
                            .Select(x=>x[1])
                            .FirstOrDefault();

        var tokenUrl = "https://login.microsoftonline.com/nicksdemodir.onmicrosoft.com/oauth2/token";
        var postBody = new Dictionary<string, string>
        {
            {"grant_type", "authorization_code"},
            {"client_id", "40dba662-4c53-4154-a5cf-976473306060"},
            //{"redirect_uri", "sample://callback"},
            {"resource", "
https://graph.windows.net"},
            {"code", code}
        };
        using (var client = new HttpClient())
        {
            var content = new FormUrlEncodedContent(postBody);
            var result = await client.PostAsync(tokenUrl, content);
            var resultContent = await result.Content.ReadAsStringAsync();
        }
    }
}

The resultContent variable will include a JSON string which consists of an access_token, refresh_token etc.

image

In order to extract the access_token, the resultContent can be deserialized to an entity:

public class TokenData
{
    public string expires_in { get; set; }
    public string access_token { get; set; }
    public string refresh_token { get; set; }
    public string id_token { get; set; }
}

var resultContent = await result.Content.ReadAsStringAsync();
var token =  JsonConvert.DeserializeObject<TokenData>(resultContent);

Finally, the token.access_token value can be used to make a call to the Azure Graph API:

var graphUri = "https://graph.windows.net/me?api-version=1.6";
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",token.access_token);
var graphProfile = await client.GetStringAsync(graphUri);

The following screenshot shows the resulting profile information that’s returned from the Graph API

image

Note that the access token is relatively short lived. The refresh token can be used to renew the access token or to retrieve an access token for an alternative resource.

Azure Active Directory and Google OAuth 2.0 Endpoints

There are a lot of arguments for and against using pre-built SDKs for doing OAuth authentication with Azure AD and Google. Having worked with the ADAL library for Azure quite a bit I think the team have done a reasonable job, especially considering it now works across the three mobile platforms (iOS, Android and Windows), and works with a PCL that is .NET Standard based. However, using any library does force you into working within the bounds of the library. For example, we recently found two shortcomings in the library:

- It doesn’t provide a good solution for doing a browser based workflow for signing in – instead it uses a webview hosted within the context of the application (depending on the platform this may be augmented with more security, for example the Web Authentication Broker - https://msdn.microsoft.com/en-us/library/windows/apps/windows.security.authentication.web.webauthenticationbroker.aspx). A browser based workflow involves launching the external browser for the user to sign in; upon successful sign on, the user is redirected back to the app.

- It doesn’t provide a mechanism to clear cached credentials. Whilst the tokens can be cleared, this doesn’t clear the cookies held within the hosted webview, which can lead to issues if the application is multi-tenanted.

If the provided libraries don’t align with what you want, you may have to roll your own solution. The Authorization Code workflow requires two endpoints:

- Authorize URL – this is the URL that you navigate the user to in order for them to sign into your application. After signing in an Authorization Code is returned to the application

- Token URL – this is the URL that the application does a POST request to in order to convert the Authorization Code into an access token.

For Azure Active Directory, these endpoints are:

Authorize - https://login.microsoftonline.com/{tenantId}/oauth2/authorize

Token - https://login.microsoftonline.com/{tenantId}/oauth2/token

For Google, these endpoints are:

Authorize - https://accounts.google.com/o/oauth2/v2/auth

Token - https://www.googleapis.com/oauth2/v4/token

As both services conform to OAuth/OpenID Connect, the parameters are the same, although there are some variations on the values that you need to supply for scope and client id.

Useful OAuth, OpenID Connect, Azure Active Directory and Google Authentication Links

Over the past couple of weeks I’ve been assisting with the development work of an enterprise system that uses both Azure Active Directory (Azure AD) and Google to authenticate users. It’s a cross platform solution which means we need code that works across both authentication platforms, and the three mobile platforms. Unfortunately this is easier said than done – The Azure AD team have done a reasonable job with the ADAL library but it’s not like we can repurpose that library for authenticating against Google. This is a tad annoying since both Azure AD and Google both use OAuth and OpenID Connect, so you’d expect there to be a good library that would work across both.

In trying to find a workable solution I can across a number of links that I want to bookmark here for future reference:

OAuth 2

Home - https://oauth.net/2/

The OAuth home page is a good starting point if you want to get more links and information about OAuth (1 and 2) but I actually found it’s main use for me was to point at the OAuth 2.0 Framework RFC

OAuth 2.0 Framework RFC - https://tools.ietf.org/html/rfc6749

You can think of the OAuth 2.0 Framework RFC as being the specification for OAuth 2.0. There are some extensions and other standards that relate to OAuth 2.0 but this is a must read if you want to understand what OAuth 2.0 is all about. You may need to refer back to this when reading other blogs/tutorials as it can help clarify what each of the roles and responsibilities are in the process.

Simple overview of OAuth 2 - https://aaronparecki.com/2012/07/29/2/oauth2-simplified

This overview provides a quick summary of the various flows for OAuth 2.0. However, I disagree with the use of the implicit workflow for mobile applications. Whilst mobile applications are not “trusted,” which would normally imply the use of the implicit workflow, the reality is that the implicit workflow can’t issue refresh tokens. This means that unless you want your users to have to log in each time they use your mobile application, you need to use the Authorization Code workflow (the client secret shouldn’t be required when requesting access tokens for mobile apps – this depends on which authentication provider you’re using).

 

OpenID Connect

Home - http://openid.net/connect/

The OpenID Connect home page is again a good starting point as it links to the many different parts of the OpenID Connect standard. OpenID Connect builds on top of OAuth 2.0 in order to provide a mechanism for users to be authenticated as well as authorized for resource access. In addition to the creation of access tokens, OpenID Connect defines an id_token which can be issued in absence of any resource that is just used to identify the user that has authenticated.

OpenID Connect Core 1.0 - http://openid.net/specs/openid-connect-core-1_0.html

This is the core specification of OpenID Connect. Similar to the specification for OAuth, this is worth both a read and to be used as a reference when working with OpenID Connect implementations.

OpenID Connect Session Management 1.0 - http://openid.net/specs/openid-connect-session-1_0.html

Whilst still in draft this standard covers how implementers are supposed to handle log out scenarios, which is useful as your application can’t simply delete it’s access tokens when a user opts to log out. Ideally when a user logs out, you’d want to make sure both cached tokens are cleared, along with invalidating any access or refresh tokens.

 

Google

OAuth 2.0 Overview - https://developers.google.com/identity/protocols/OAuth2

OpenID Connect - https://developers.google.com/identity/protocols/OpenIDConnect

Google’s documentation isn’t too bad but does require you to read all of the pages as the OAuth and OpenID Connect implementation details seem to be scattered across the pages. The assumption is that for any given type of application you can simply read the one page – unfortunately, if you want to get an understanding of the Google implementation, you really need to read everything. Authenticating/authorizing with Google is significantly simpler than with Azure AD as there is no notion of linking your application registration with specific permissions to other applications registered with Azure AD. This is a significant limitation of using Google sign in, as you can really only use it to authenticate and then use the token to access various Google APIs.

 

Azure Active Directory

Azure AD Developer’s Guide - https://docs.microsoft.com/en-au/azure/active-directory/develop/active-directory-developers-guide

Authentication Scenarios for Azure AD - https://docs.microsoft.com/en-au/azure/active-directory/develop/active-directory-authentication-scenarios

Azure AD is a much longer read, and it’s very easy to get lost in the world of application configuration and settings. My recommendation is to start with something simple, and then grow from that. For examples, start by authenticating a use to sign into your mobile app, then extend it so that you can use the access token to connect to a Web API, and then on to retrieve information from other Microsoft services within the Web API, and then perhaps make it all multi-tenanted (that’s one for another post!).