Identity in the cloud

Identity management in the cloud is a totally different ball game to when everything was installed and accessed on the corporate network. Users in the enterprise authenticated with an on-premises directory service (e.g. Active Directory Domain Services) and this determined the apps and data they had access to. Occasionally, cross-forest federations were established to allow users belonging to one corporate domain to access resources in another.

Nowadays, with the proliferation of apps and services available in the cloud and the speed and ease with which we consume them, managing cross-forest trusts yourself is mostly a thing of the past. Identities still need to be managed in the cloud, but on-premises domain services alone don’t work when more and more apps are being delivered as multi-tenant SaaS that lives outside of your datacenter.

Azure Active Directory

On the public internet, where most modern software now operates, identity and access management is being consolidated around a few key multi-tenant identity services. Azure Active Directory, not to be confused with its on-premises cousin, is one of these.

The usual route for corporate entities looking to manage identity in the cloud is to synchronize their on-premises directory with Azure AD. From there, Azure AD can broker trusts between users and registered applications/services without the need for old-school federations.

Recently, I’ve been putting a lot of effort into learning how applications authenticate with Azure AD and make use of protected resources, owned by different applications, that also trust Azure AD. I’m going to share how a typical web application does this.

OpenID Connect

OpenID Connect is a standard authentication protocol for delegating access to user data (or some other protected resource) to client applications. It is an extension of the well-known OAuth 2.0 authorization framework, adding only some identity verification features. It operates over a RESTful HTTP API making it ideal for applications accessed over the internet, so most modern applications.

Authenticating with Azure AD is just like authenticating against any other OpenID Connect server. In fact, the only part of my sample code that you could directly associate with Azure AD itself is the authority URI used. This is a good thing for application developers because the same code can be used to authenticate with any authority that also supports OpenID Connect.

The protocol supports multiple “flows” for different authentication scenarios. The one I’m going to talk you through is the most common one, the authorization code flow.

Understanding Authorization Code Flow

The authorization code flow is used for delegating access from a user to a server-side client application, allowing it to access protected resources on the user’s behalf. This flow is most often used with Web APIs and MVC apps that can securely hold a client secret on the web server. Apps that operate directly on a user device like a mobile phone, something beyond the developer’s remit to secure, should use a different flow.

With authorization code flow, the client application redirects the user to the authentication authority (Azure AD or another), the user logs in and grants access, receives an authorization code and is redirected back to the client application. The client application then uses this authorization code and its own client credentials to obtain an access token for the user. It can then access protected resources with this access token.

A visual might help here:

AuthCodeFlow

This looks a little busy, but it isn’t so difficult to wrap your head around. Let’s go through that again, step-by-step.

  1. The user accesses a client web application.
  2. The application issues an authorization challenge.
  3. The user is redirected to Azure AD in their browser to sign in with their existing user account, e.g. jane.bloggs@contoso.com and password. They are asked to grant access to the specific things that the client application needs access to, defined through scopes.
  4. Sign in and granting of access by the user is successful, this produces an authorization code.
  5. The user is redirected back to the client application with the authorization code.
  6. The client application authenticates against Azure AD with the authorization code from the user and it’s own client credentials, an identity/shared secret for the application already known to Azure AD.
  7. If successful, Azure AD supplies the client application with a user access token. This contains claims that correspond to the scopes that the user previously approved access for.
  8. The client application uses the access token to access protected resources that it has a claim for. This is usually done by sending HTTP requests to the application servicing the protected resources, with the access token in the Authorization header of those requests.
  9. The application servicing the protected resources, most likely a Web API, accepts the access token (understanding the claims) and returns the requested resource.

This flow has several key security benefits for a hosted application:

  • The client application never handles the user’s Azure AD credentials. This is great both for the user and the application developer!
  • The access token gets transmitted directly to the client application, without going through the user’s device and potentially exposing it to something else.

Demo Application

That’s the theory over with – if you want more you can go straight to RFC 6749. Now we will turn our attention to the kind of code a client application will need to allow a user to sign-in with Azure AD and access their protected resources with an access token.

Introducing ToDoGraphDemo

The demo app I have written is a simple to do list app written with ASP.NET MVC, it synchronizes a task list with a OneNote page associated with the user. Office 365 makes a good test bed for this because all Office 365 access is federated with Azure AD. Even if you’ve never directly used Azure infrastructure before, if you have an Office 365 administrator account you can log into Azure Portal and see the Azure AD instance you got for free.

With my demo app, it is the client application. The user is anybody with an Office 365 account that includes OneNote, the authentication authority is Azure AD and the protected resource is the user’s OneNote data. The application that accepts the access tokens and services that resource is Microsoft Graph API, a RESTful API covering many Microsoft products. I’m going to cover the app’s use of Graph API in a separate post, if that interests you also.

The full code listing for the demo is available on GitHub.

Registering the client application

You can rely on the user to bring their credentials and you can rely on successful authentication to produce an authorization code, but we still need client credentials to request an access token. Our first step, therefore, is to register our app in the Microsoft Application Registration Portal.

You can log into the portal with any Microsoft account (personal or work). Once signed in, go through the Add an app wizard. There is a guided path for ASP.NET MVC that will give you the code you need to authenticate with Azure AD. However, the OWIN Startup class that wizard gives you does not request an access token, copy it out anyway because it’s a useful starting point. I kept the View My Claims page from this example.

Once the application exists in the My applications list, select it. You will be taken to a more detailed options page for your app registration. Under Application Secrets, select Generate New Password and copy the generated password to your clipboard. The combination of the Application Id and this password are the client credentials needed to generate an access token.

Editing Web.config

Following the portal wizard will get you a Web.config file in your solution containing your ClientId, RedirectUri, Tenant and Authority configuration properties. Add a new property, ClientSecret, whose value is the password you copied to the clipboard earlier.

The appSettings element in my Web.config looks as follows:

<appSettings>
    <add key="webpages:Version" value="3.0.0.0"/>
    <add key="webpages:Enabled" value="false"/>
    <add key="ClientValidationEnabled" value="true"/>
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
    <add key="ClientId" value="8f083dd5-032d-4f10-8af4-b00903eb8680" />
    <add key="ClientSecret" value="abc****************************" />
    <add key="RedirectUri" value="https://localhost:44378/" />
    <add key="Tenant" value="anchorloop.com" />
    <add key="Authority" value="https://login.microsoftonline.com/{0}/v2.0" />
    <!-- Allows the app to read and modify notebooks, sections and pages on behalf of the user -->
    <add key="Scopes" value="Notes.ReadWrite" />
  </appSettings>

Note: ClientId and ClientSecret above are not my real values, they’re a secret after all!

This is a good time to revise the purpose of each property:

  • ClientId is the Application Id of the registered app in the registration portal.
  • ClientSecret is a generated password associated with the registered application in the portal.
  • RedirectUri is where the user will be redirected, with the authorization code, after successfully signing in. Mine is a localhost address for testing on my local machine, but either way, it should be secured with TLS and accessed via HTTPS. If the transport layer were not encrypted at this point, it would undermine the whole protocol as a security mechanism.
  • Tenant allows you to restrict the set of users that can sign-in down to a single Azure AD tenant. The wizard example asks you to set this to common, which allows all Microsoft accounts to sign in. My app requires an Office 365 subscription, so I set this to be my tenant only.
  • Authority is the link (or more accurately, a template of the link) to the authentication service responsible for the exchange. This link is known as the Azure AD v2 endpoint.
  • Scopes is a space delimited string listing the scopes the application requires. These state to Azure AD exactly what the application is intended to access and these will be presented to the user transparently for approval the first time they sign-in to the app. This demo requires read and write access to OneNote to synchronize the task list, the appropriate scope for this is Notes.ReadWrite. If the app tried to use its access token to access anything it does not have a scope for, it would be denied.

Here is a screenshot of what the user sees the first time they successfully sign-in to the app via Azure AD:

permissions_and_claims

You can see how the user is asked to consider the claims requested by the app before an authorization code can be generated.

An OWIN Startup class for Authorization Code Flow

Here is the code listing for my OWIN Startup class, that uses the above settings:

public class Startup
{
    // The Client ID is used by the application to uniquely identify itself to Azure AD.
    string clientId = ConfigurationManager.AppSettings["ClientId"];
    // RedirectUri is the URL where the user will be redirected to after they sign in.
    string redirectUri = ConfigurationManager.AppSettings["RedirectUri"];
    // Tenant is the tenant ID (e.g. contoso.onmicrosoft.com, or 'common' for multi-tenant)
    static string tenant = ConfigurationManager.AppSettings["Tenant"];
    // Authority is the URL for authority, composed by Azure Active Directory v2 endpoint
    // and the tenant name (e.g. https://login.microsoftonline.com/contoso.onmicrosoft.com/v2.0)
    string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture,
        ConfigurationManager.AppSettings["Authority"], tenant);
    // Scopes are the specific permissions we are requesting for the application.
    string scopes = ConfigurationManager.AppSettings["Scopes"];
    // ClientSecret is a password associated with the application in the authority.
    // It is used to obtain an access token for the user on server-side apps.
    string clientSecret = ConfigurationManager.AppSettings["ClientSecret"];

    public void Configuration(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            ClientId = clientId,
            Authority = authority,
            RedirectUri = redirectUri,
            PostLogoutRedirectUri = redirectUri,
            Scope = "openid email profile offline_access " + scopes,

            // TokenValidationParameters allows you to control the users who are allowed to sign in
            // to your application. In this demo we only allow users associated with the specified tenant.
            // If ValidateIssuer is set to false, anybody with a personal or work Microsoft account can
            // sign in.
            TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters()
            {
                ValidateIssuer = true,
                ValidIssuer = tenant
            },

            // OpenIdConnect event handlers/callbacks.
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthorizationCodeReceived = OnAuthorizationCodeReceived,
                AuthenticationFailed = OnAuthenticationFailed
            }
        });
    }

    private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
    {
        string userId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
        TokenCache userTokenCache = new SessionTokenCache(
            userId, context.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
        // A ConfidentialClientApplication is a server-side client application that can securely store a client secret,
        // which is not accessible by the user.
        ConfidentialClientApplication cca = new ConfidentialClientApplication(
            clientId, redirectUri, new ClientCredential(clientSecret), userTokenCache, null);
        string[] scopes = this.scopes.Split(new char[] { ' ' });

        AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(context.Code, scopes);
    }

    private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
    {
        context.HandleResponse();
        context.Response.Redirect("/?errormessage=" + context.Exception.Message);
        return Task.FromResult(0);
    }
}

The meat of the class is in the Configuration method, which sets the authentication options in the OWIN middleware. This sets OpenID Connect as the authentication mechanism and the use of cookies. This method is similar to the one the registration portal wizard will guide you to write, the differences are the addition of my extra scopes from Web.config and an additional notification handler: OnAuthorizationCodeReceived. This is where we move on from the wizard’s code and trade the authorization code for an access token.

Authorization code handler

Now we’re using classes from MSAL, the Microsoft Authentication Library [NuGet]. On that note, Azure AD’s authentication libraries have a confusing history that you should be aware of. Before MSAL there was ADAL (Active Directory Authentication Library) and if you’re googling around for Azure AD authentication you will probably land on that because it has been around longer. However, that’s the library for “classic” Azure AD auth, using the v1 endpoint.

MSAL has more capabilities: it can enable authentication with Azure AD, any other Microsoft account and Azure AD B2C (business to consumer). Technically, MSAL is in the “preview” status, but only in the sense that Microsoft is not yet ready to commit to backward compatibility between all of the early versions. They do insist it is ready for production use, which is confusing because in most other contexts “preview” means: “available for general hackery but not yet ready for production use”. For what it’s worth, I think committing to MSAL is the way to go, that seems to be where the development effort is going. Anyway, back to the code.

Let’s look at this handler again without the rest of the Startup class:

private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
    string userId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
    TokenCache userTokenCache = new SessionTokenCache(
        userId, context.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
    // A ConfidentialClientApplication is a server-side client application that can securely store a client secret,
    // which is not accessible by the user.
    ConfidentialClientApplication cca = new ConfidentialClientApplication(
        clientId, redirectUri, new ClientCredential(clientSecret), userTokenCache, null);
    string[] scopes = this.scopes.Split(new char[] { ' ' });

    AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(context.Code, scopes);
}

The object model for MSAL departs from the OAuth 2.0 primitives a bit. We first create a token cache, which allows us to securely store the access token of the user for its lifetime, these are created on a per-user basis. We’ll go through the cache in a little more detail next.

Next, we create a ConfidentialClientApplication object, this corresponds to the kind of client that acquires an access token with the authorization code flow. Another type defined in MSAL is PublicClientApplication, which is intended for client-side apps that use the implicit flow instead. The ConfidentialClientApplication knows how to request an access token from the authorization code received when the event is triggered, our required scopes and it’s own client credentials. The token lives in the cache securely until we want to use it.

Defining a token cache

The SessionTokenCache actually isn’t included in MSAL. In the interests of transparency, I lifted the class from a code sample written by the Graph API team on GitHub and haven’t really changed it. I’ll avoid explaining the class in great detail like I wrote it myself, but basically, it’s a wrapper around the TokenCache object that synchronizes the persistent token cache with an ASP.NET user session using a read-write lock for thread safety.

I’m surprised Microsoft didn’t include something like this in the library for convenience, it seems to me this wrapper meets a use case that most users would have. I suspect this is because the HttpContextBase object the class uses is not part of .NET Core and they didn’t have a great multi-platform solution for marrying the cache with session state. Anyway, all credit to Microsoft for the code sample, it’s worked fine for me so far.

Using the access token

We now have all the code we need to acquire an access token for a user from Azure AD and use it to access the user’s data. As mentioned earlier, I’m going to cover ToDoGraphDemo’s use of Graph API with a separate blog post (because I’ve gone on long enough already), but I will at least close by showing you how to retrieve the access token from the cache and attach it to a HTTP request.

The following is a function from my GraphAuthProvider class:

public async Task<string> GetUserAccessTokenAsync()
{
    string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
    HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
    TokenCache userTokenCache = new SessionTokenCache(userId, httpContext).GetMsalCacheInstance();

    ConfidentialClientApplication cca = new ConfidentialClientApplication(
        clientId, redirectUri, new ClientCredential(clientSecret), userTokenCache, null);

    // Attempt to retrieve access token from the cache. Could also make a network call for a new
    // access token if the cached one is expired or close to expiration and a refresh token is
    // available.
    try
    {
        AuthenticationResult result = await cca.AcquireTokenSilentAsync(
            scopes.Split(new char[] { ' ' }), cca.Users.First());
        return result.AccessToken;
    }
    // Unable to retrieve the access token silently.
    catch (Exception)
    {
        httpContext.Current.Request.GetOwinContext().Authentication.Challenge(
            new AuthenticationProperties() { RedirectUri = "/" },
            OpenIdConnectAuthenticationDefaults.AuthenticationType);

        throw new Microsoft.Graph.ServiceException(
            new Microsoft.Graph.Error
            {
                Code = Microsoft.Graph.GraphErrorCode.AuthenticationFailure.ToString(),
                Message = "Authentication is required."
            });
        }
    }

It rebuilds the ConfidentialClientApplication object, passing in the session cache, which is then used to acquire an access token silently. If the cached token has expired, the ConfidentialClientApplication may be able to use a refresh token and a round-trip to Azure AD to acquire a new access token, without requiring the user to sign in again.

The returned string can be supplied in the Authorization header of any HTTP request that accesses protected resources on behalf of the user.

// Get access token.
string accessToken = await GraphAuthProvider.Instance.GetUserAccessTokenAsync();

// Set access token to HTTP auth header.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);

Next time

That covers everything you need to sign-in and delegate access to a client application with Azure AD. My follow-up to this post covers the app’s use of Graph API (via an OData client) and the access tokens acquired by the above code.

You can view that follow-up here: Automating Office 365 with Microsoft Graph API

Useful resources and acknowledgments

The following links were helpful in my own experimentation with Azure AD authentication, inspired various parts of my demo app code and/or contributed to my understanding. They can certainly help you too.

Hat-tip to the authors of the above.

About the Author Kirk MacPhee

An experienced software developer and technical lead, specializing in automation technologies and their application.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s