Export (0) Print
Expand All

Securing a Windows Store Application and REST Web Service Using Azure AD (Preview)

Published: April 22, 2013

Updated: May 12, 2014

noteNote
This sample is outdated. Its technology, methods, and/or user interface instructions have been replaced by newer features. To see an updated sample that builds a similar application, see NativeClient-WindowsStore.

This walkthrough will show you how to develop a Windows Store application that is authorized to call a web API on behalf of an Azure AD user. Both the native client application and the web API are registered in Azure AD by a tenant administrator. This administrator can control the relationship between the client and service, including assigning individual permissions for each. Authorization is handled by using the OAuth 2.0 bearer grant type, and Azure AD issues refresh tokens that make it possible to minimize the frequency that a user needs to re-enter their credentials.

The finished product of this walkthrough closely resembles the AAL for Windows Store code sample, which is available on the MSDN Code Gallery: Code Sample: Windows Store Application to REST Service - Authentication with AAD via Browser Dialog

This walkthrough will cover:

  • Building a resource web API. This service will be an MVC 4 Web API that requires Azure AD authentication.

  • Building a client application. The native client will be a Windows Store application.

  • Registering the native client and web API with Azure AD. The native client will also be granted access to call the web API.

noteNote
This walkthrough uses AAL for Windows Store, which is currently in developer preview. As a pre-release product, its functionality is subject to change in future releases.

The following prerequisites must be met prior starting the walkthrough:

  • An Internet connection

  • An active Azure subscription: You can get a 90 day free trial here: Azure Free Trial

  • Visual Studio 2012 Professional or Visual Studio 2012 Ultimate: You can download a free trial here: Visual Studio Free Trial

  • Windows 8: To develop this application, you need to be using Windows 8 and also have registered for a developer license. For more information

To demonstrate the use of a native client with Azure AD, we will build Windows Store application using Azure Authentication Library (AAL) that allows a user to authorize access to a REST service with their Azure AD account.

As part of the walkthrough we will also build a simple service that the client can call. The service provides a central location for a user to save their ToDo List, this service can be hosted on-premise or in Azure.

The below figure illustrates the roles of the parties involved.

  1. The Contoso ToDo List Service, stores user’s ToDo List items. This is a Web API that allows user to save and get their items. A user cannot manage the items of another user.

  2. The Contoso ToDo List Client app, allows a user to access the service using Azure AD, view and create ToDo List items.

  3. The Azure AD tenant created by the organization, in this case Contoso. The tenant contains the security principals for the user, app and service.

Solution Architecture

The first thing we need to do in our walkthrough is ensuring that you have an Azure AD tenant to work with in the Azure Management Portal.

Azure Active Directory is the service that provides the identity backbone of Microsoft offerings such as Office 365 and Intune. If you subscribe to any of those services, you already have an Azure AD tenant that your users use to sign in. If you want to reuse that tenant, as of today you can do by creating a new Azure subscription and using ad administrator user from that tenant at sign up time. This walkthrough will not provide detailed guidance on how to perform that process.

In this walkthrough we will focus on the case in which you do not have an existing Azure AD tenant. The first thing we need to do is to ensure that you have an Azure AD tenant to work with in the Azure Management Portal. The Azure Management Portal provides a mechanism for you to create a new tenant directly from the Management Portal’s pages.

  1. Navigate to http://manage.windowsazure.com and sign in with the Microsoft Account associated to your Azure subscription. When you have signed in, look through the list of tabs on the left hand side of the screen and locate the Active Directory tab; click it.



    Add an Enterprise Directory

    The screen you see here is Active Directory’s home page in the Azure Management Portal. The top area offers two different headers. The Access Control Namespaces header refers to the namespaces created via the ACS 2.0 service; this walkthrough will not focus on that part. The Directory header leads to the area of the Management Portal featuring operations on directory tenants; that’s where most of our activities will take place.

  2. As shown in the screenshot above, the Azure Management Portal detects that your user does not have any directory tenant associated to it and offers you the chance of creating one. Click Create Your Directory.



    Add Enterprise Tenant

    You are presented with a dialog which will gather the essential information needed to create a directory tenant for you. Let’s go through the various fields, bottom to top:

    • Organization Name: This field is required, and its value will be used as a moniker whenever there’s the need to display the company name.

    • Country or Region: The value selected in this dropdown will determine where your tenant will be created. Given that the directory will store sensitive information, please do take into account the normative about privacy of the country in which your company operates.

    • Domain Name: This field represents a critical piece of information: it is the part of the directory tenant domain name that is specific to your tenant, what distinguishes it from every other directory tenant.

      At creation, every directory tenant is identified by a domain of the form <tenantname>.onmicrosoft.com. That domain is used in the UPN of all the directory users and in general wherever it is necessary to identify your directory tenant. After creation it is possible to register additional domains that you own. For more information, see domain management.

      The Domain Name must be unique: the UI validation logic will help you to pick a unique value. It is recommended that you choose a handle which refers to your company, as that will help users and partners as they interact with the directory tenant.

    Filling up that dialog is all it takes to create a directory tenant. As soon as you click the check button on the lower right corner, Azure AD creates a new tenant for you according to the parameters specified. You can see the new entry below.



    Enterprise Directory

    noteNote
    When a directory tenant is created, it is configured to store users and credentials in the cloud. If you want to integrate your directory tenant with your on-premises deployment of Windows Server Active Directory, you can find detailed instructions here.

  3. Click on the newly created directory entry to display the user management UI.



    All Tenant Users

    The directory tenant is initially empty, except for the Microsoft Account administering the Azure subscription in which the new tenant was created.

    That Microsoft Account is listed here to signal that it has Global Administrator privileges for the tenant, however that holds only for operations performed through the Azure Management Portal UI. That Microsoft Account cannot actually authenticate against the directory tenant for flows such as web SSO, hence it cannot be used as a test user for our web SSO walkthrough.

    Let’s add a new user to the directory, so that we will be able to exercise the web SSO scenario later in the document.

  4. Click on the Add User command in the bar at the bottom of the screen. You’ll be presented with the dialog depicted below.



    Add a User

    The Add User dialog begins by asking you if you want to create a directory user or if you want to add an existing Microsoft Account (which will have the same limitations as the Microsoft Account currently in use for administering the subscription). We need a directory user for our workflow, hence let’s go ahead and pick the New user in your organization entry.

  5. Choose a username, then click the arrow on the bottom-right corner.



    Add User

    In the next screen you can choose some basic user attributes. The role you assign to the user will determine what the user can do when accessing the directory.

    You will be required to provide a valid email for the account. You will also have the option to try multi-factor authentication preview.

  6. Once you filled the values in, click the arrow on the bottom right corner to move to the next screen.



    Temporary Password

    In the last step, the Management Portal generates a temporary password, which will have to be used at the time of the first login. At that time the user will be forced to change password. Please save the temporary password somewhere, as we’ll need it once we’ll have all the components in place to test the scenario.

    At this point we have everything we need for providing an authentication authority in our web SSO scenario: a directory tenant and a valid user in it.

In this section, you will create a ToDo list service that maintains ToDo items for Contoso employees. The service will consist of an ASP.NET Web API and will use the JSON Web Token Handler to ensure only authorized users and clients can access the service.

  1. Start Visual Studio, click File, click New, and then click Project.

  2. In the New Project dialog, select Web from the Visual C# templates on the left menu, then select the ASP.NET MVC 4 Web Application template. Name the new project “ToDoListService”, name the solution “ToDoListApp,” and then click OK.

  3. The New ASP.NET MVC 4 Project dialog will appear. Select the Web API template, and then click OK. The Web API template creates the required scaffolding for a simple Web API project.



    New ASP.NET MVC 4 Project

  4. After the new project has been created, press F5 to run the project. A browser should open and show a page that looks like the following screenshot:



    The resulting new ASP.NET project

    In the open web browser, you’ll notice the URL that Visual Studio automatically assigned for the application, for example http://localhost:29062/ in the image above. You will need to use this value later because it represents the following configuration values:

    • Audience in the Todo List Service project

    • App ID Uri registered in Azure AD, and used by the ToDo List Client to identify the service

    For a production application, the value is recommended to be the base address of the site hosting the application, for example: https://todolistservice.azurewebsites.net. However, it can be any URI that is unique within the tenant.

    ImportantImportant
    A production service must use SSL to protect communication to the service to prevent an attacker from stealing the authentication token and performing an elevation-of-privilege attack.

  5. Close the web browser and proceed on to the next step.

Now you will add the required dependencies to your project.

  1. Right-click References in Solution Explorer, then click Add Reference.

  2. On the Reference Manager dialog, select the System.IdentityModel assembly, and then click OK. The reference will be added to your project.

  3. Install the JSON Web Token Handler NuGet package by right-clicking the References folder, then clicking “Manage NuGet Packages…”.

  4. In the Manage NuGet Packages dialog, search for ‘jwt handler.’ When the results appear, select the JSON Web Token Handler for the Microsoft .NET Framework and click Install.

  5. A License Acceptance dialog will appear. If you agree to the license terms, click I Accept. The JWT Handler will be downloaded and referenced in your project.

  6. In the same Manage NuGet Packages dialog, search for ‘token validation extension’. When the results appear, select the Microsoft Token Validation Extension for Microsoft .NET Framework 4.5 and click Install.

  7. A License Acceptance dialog will appear. If you agree to the license terms, click I Accept. The Token Validation Extension will be downloaded and referenced in your project.

Now that we have added the required dependencies, we need to update the Global.asax.cs file to start using the JWT Handler to validate tokens issued by Azure AD. The token will be read from the authorization header when a client calls the Web API service.

The token is validated to ensure that:

  • The token issuer is correct, and is the issuer for your Azure AD tenant

  • The token is cryptographically signed with the correct x509 certificate

  • The token grants the required permissions to the calling application

The full contents of the updated Global.asax.cs file is shown below, followed by a description of the changes.

using System;
using System.Collections.Generic;
using System.IdentityModel.Metadata;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Xml;
using System.Xml.Linq;

namespace ToDoListService
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801

    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            GlobalConfiguration.Configuration.MessageHandlers.Add(new TokenValidationHandler());
        }
    }

    internal class TokenValidationHandler : DelegatingHandler
    {
        // Domain name or Tenant name
        const string domainName = "[company/domain name like treyresearch.onmicrosoft.com]";
        const string audience = "[service App ID URI configured in tenant/domain for the Service App ex: http://localhost:40316/]";

        static DateTime _stsMetadataRetrievalTime= DateTime.MinValue;
        static List<X509SecurityToken> _signingTokens = null;
        static string _issuer = string.Empty;
        

        // SendAsync is used to validate incoming requests contain a valid access token, and sets the current user identity 
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            string jwtToken;
            string issuer;
            List<X509SecurityToken> signingTokens;

            if (!TryRetrieveToken(request, out jwtToken))
            {
                return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.Unauthorized));
            }

            try
            {
                // Get tenant information that's used to validate incoming jwt tokens
                GetTenantInformation(string.Format("https://login.windows.net/{0}/federationmetadata/2007-06/federationmetadata.xml", domainName), out issuer, out signingTokens);
            }
            catch (Exception)
            {
                return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.InternalServerError));
            }

            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler()
            {
                CertificateValidator = X509CertificateValidator.None
            };

            TokenValidationParameters validationParameters = new TokenValidationParameters
            {
                AllowedAudience = audience,
                ValidIssuer = issuer,
                SigningTokens = signingTokens
            };

            try
            {

                 // Validate token
                ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtToken,
                                                validationParameters);

                //set the ClaimsPrincipal on the current thread.
                Thread.CurrentPrincipal = claimsPrincipal;

                // set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment.
                if (HttpContext.Current != null)
                {
                    HttpContext.Current.User = claimsPrincipal;
                }            

                // Verify that required permission is set in the scope claim
                if (ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value != "user_impersonation")
                {
                    return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.Unauthorized));
                }

                return base.SendAsync(request, cancellationToken);
            }
            catch (SecurityTokenValidationException)
            {
                return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.Unauthorized));
            }
            catch (Exception)
            {
                return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.InternalServerError));
            }
        }

        // Reads the token from the authorization header on the incoming request
        private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
        {
            token = null;
            string authzHeader;

            if (!request.Headers.Contains("Authorization"))
            {
                return false;
            }

            authzHeader = request.Headers.GetValues("Authorization").First<string>();

            // Verify Authorization header contains 'Bearer' scheme
            token = authzHeader.StartsWith("Bearer ") ? authzHeader.Split(' ')[1] : null;

            if (null == token)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Parses the federation metadata document and gets issuer Name and Signing Certificates
        /// </summary>
        /// <param name="metadataAddress">URL of the Federation Metadata document</param>
        /// <param name="issuer">Issuer Name</param>
        /// <param name="signingTokens">Signing Certificates in the form of X509SecurityToken</param>
        static void GetTenantInformation(string metadataAddress, out string issuer, out List<X509SecurityToken> signingTokens)
        {
            signingTokens = new List<X509SecurityToken>();

            // The issuer and signingTokens are cached for 24 hours. They are updated if any of the conditions in the if condition is true.            
            if (DateTime.UtcNow.Subtract(_stsMetadataRetrievalTime).TotalHours > 24
                || string.IsNullOrEmpty(_issuer)
                || _signingTokens == null)
            {
                MetadataSerializer serializer = new MetadataSerializer()
                {
                    CertificateValidationMode = X509CertificateValidationMode.None
                };
                MetadataBase metadata = serializer.ReadMetadata(XmlReader.Create(metadataAddress));

                EntityDescriptor entityDescriptor = (EntityDescriptor)metadata;

                // get the issuer name
                if (!string.IsNullOrWhiteSpace(entityDescriptor.EntityId.Id))
                {
                    _issuer = entityDescriptor.EntityId.Id;
                }

                // get the signing certs
                _signingTokens = ReadSigningCertsFromMetadata(entityDescriptor);

                _stsMetadataRetrievalTime = DateTime.UtcNow;
            }

            issuer = _issuer;
            signingTokens = _signingTokens;
        }

        static List<X509SecurityToken> ReadSigningCertsFromMetadata(EntityDescriptor entityDescriptor)
        {
            List<X509SecurityToken> stsSigningTokens = new List<X509SecurityToken>();

            SecurityTokenServiceDescriptor stsd = entityDescriptor.RoleDescriptors.OfType<SecurityTokenServiceDescriptor>().First();

            if (stsd != null && stsd.Keys != null)
            {
                IEnumerable<X509RawDataKeyIdentifierClause> x509DataClauses = stsd.Keys.Where(key => key.KeyInfo != null && (key.Use == KeyType.Signing || key.Use == KeyType.Unspecified)).
                                                             Select(key => key.KeyInfo.OfType<X509RawDataKeyIdentifierClause>().First());

                stsSigningTokens.AddRange(x509DataClauses.Select(clause => new X509SecurityToken(new X509Certificate2(clause.GetX509RawData()))));
            }
            else
            {
                throw new InvalidOperationException("There is no RoleDescriptor of type SecurityTokenServiceType in the metadata");
            }

            return stsSigningTokens;
        }
    }
}


Before running this code, you need to set the following constants:

  • domainName: This is the domain name you registered for your Azure AD tenant. For example: contoso.onmicrosoft.com

  • audience: This is the ToDo list service’s App ID URI, which we will register with Azure AD later. A standard value for this constant is the base address where your ToDo list service is running. For example, when developing in Visual Studio: http://localhost:29062/

We recommend using the base address as the audience value for the service because it allows callers to derive the identity of the target service from the service address.

Now the code shown above will be discussed in more detail.

In Global.asax.cs the token handler is configured in the pipeline to:

  • Acquire the token from the authorization header in the request

  • Retrieve the metadata from the tenant’s security token service

  • Use the signing key and issuer from the metadata to validate the token

  • Set the identity of the current thread to be the user identity in the token

  • Verify that calling application (e.g. the ToDo List client) has been granted the required permissions to call the Web API on the user’s behalf

The following using directives were added to the Global.asax.cs file:

using System;
using System.Collections.Generic;
using System.IdentityModel.Metadata;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Xml;
using System.Xml.Linq;

Next, the TokenValidationHandler class was added, which derives from the DelegatingHandler class. TokenValidationHandler performs most of the work to validate the tokens used to authenticate to the service.

To start writing the handler, add the following code:

internal class TokenValidationHandler : DelegatingHandler
    {
        // Domain name or Tenant name
        const string domainName = "[company/domain name like treyresearch.onmicrosoft.com]";
        const string audience = "[service App ID URI configured in tenant/domain for the Service App ex: http://localhost:40316/]";

        static DateTime _stsMetadataRetrievalTime= DateTime.MinValue;
        static List<X509SecurityToken> _signingTokens = null;
        static string _issuer = string.Empty;

Note the domainName and audience must be set.

The domainName is the domain name you registered when signing up for Azure AD. An example value is: contoso.onmicrosoft.com

The audience is the App ID URI for the ToDo list service. This value is derived from the local address where your service is running, for example: http://localhost:29062/. The App ID Uri will be set as the audience claim of the tokens used to authenticate to the service and indicates the intended target for a token. This allows the ToDo list service to verify that a token is not intended for another service and reject it, which also helps to prevent forwarding attacks.

Note that the _issuer and _signingTokens fields are not set. They will be set later using information from a federation metadata document that is hosted by Azure AD, and allows the values to be dynamically updated in the future. It is important that the application is able to update this information, especially for signing certificates, because they can change over time when certificate rollover occurs.

To read the federation metadata document, and retrieve issuer and certificate information, the following code is added:

        /// <summary>
        /// Parses the federation metadata document and gets issuer Name and Signing Certificates
        /// </summary>
        /// <param name="metadataAddress">URL of the Federation Metadata document</param>
        /// <param name="issuer">Issuer Name</param>
        /// <param name="signingTokens">Signing Certificates in the form of X509SecurityToken</param>
        static void GetTenantInformation(string metadataAddress, out string issuer, out List<X509SecurityToken> signingTokens)
        {
            signingTokens = new List<X509SecurityToken>();

            // The issuer and signingTokens are cached for 24 hours. They are updated if any of the conditions in the if condition is true.            
            if (DateTime.UtcNow.Subtract(_stsMetadataRetrievalTime).TotalHours > 24
                || string.IsNullOrEmpty(_issuer)
                || _signingTokens == null)
            {
                MetadataSerializer serializer = new MetadataSerializer()
                {
                    // turning off certificate validation for demo. Don't use this in production code.
                    CertificateValidationMode = X509CertificateValidationMode.None
                };
                MetadataBase metadata = serializer.ReadMetadata(XmlReader.Create(metadataAddress));

                EntityDescriptor entityDescriptor = (EntityDescriptor)metadata;

                // get the issuer name
                if (!string.IsNullOrWhiteSpace(entityDescriptor.EntityId.Id))
                {
                    _issuer = entityDescriptor.EntityId.Id;
                }

                // get the signing certs
                _signingTokens = ReadSigningCertsFromMetadata(entityDescriptor);

                _stsMetadataRetrievalTime = DateTime.UtcNow;
            }

            issuer = _issuer;
            signingTokens = _signingTokens;
        }

Given a URL to the federation metadata file, this code will read the contents of the file and return the desired information.

The federation metadata only needs to be read periodically, not on every request, so a simple form of metadata caching has been implemented. The metadata will be retrieved either on initial use or after a 24 hour period has passed since the last time it was read. The most critical update to the federation metadata occurs in the event of a signing certificate rollover. During rollover, the expiring and new certificate will both be available for several days. This extended period allows the service enough time to acquire both certificates.

Next, we will look at the SendAsync() method that is located in the TokenValidationHandler class.

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

SendAsync() is called on every request to the ToDo list service. It provides a way to authenticate every incoming request by ensuring the request contains a valid token, and then sets the current user identity for the authenticated user. The following code reads the token from the authorization header in the request that is being made to the ToDo list service.

if (!TryRetrieveToken(request, out jwtToken))
{
    return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}

The above code uses TryRetieveToken(), which reads the token from the header, as shown in the code below.

// Reads the token from the authorization header on the incoming request
        private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
        {
            token = null;
            string authzHeader;

            if (!request.Headers.Contains("Authorization"))
            {
                return false;
            }

            authzHeader = request.Headers.GetValues("Authorization").First<string>();

            // Verify Authorization header contains 'Bearer' scheme
            token = authzHeader.StartsWith("Bearer ") ? authzHeader.Split(' ')[1] : null;

            if (null == token)
            {
                return false;
            }

            return true;
        }

The above code checks for a token in the authorization header string. The header value must start with “Bearer”, in accordance with RFC 6750 “The OAuth 2.0 Authorization Framework: Bearer Token Usage”, which describes how to pass tokens in a web API call. If a token is found in the header, true is returned.

Once the token has been retrieved from the header, it must be validated. The validation process requires validating the token signature against the certificates retrieved from the Azure AD tenant metadata, the issuer, and the audience. The JWT Handler performs this validation based on the values set on a TokenValidationParameters object, which is created in the SendAsync() method as shown below.

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            string jwtToken;
            string issuer;
            List<X509SecurityToken> signingTokens;

            if (!TryRetrieveToken(request, out jwtToken))
            {
                return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.Unauthorized));
            }

            try
            {
                // Get tenant information that's used to validate incoming jwt tokens
                GetTenantInformation(string.Format("https://login.windows.net/{0}/federationmetadata/2007-06/federationmetadata.xml", domainName), out issuer, out signingTokens);
            }
            catch (Exception)
            {
                return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.InternalServerError));
            }

            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler()
            {
                CertificateValidator = X509CertificateValidator.None
            };

            TokenValidationParameters validationParameters = new TokenValidationParameters
            {
                AllowedAudience = audience,
                ValidIssuer = issuer,
                SigningTokens = signingTokens
            };

            try
            {

                 // Validate token
                ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtToken,
                                                validationParameters);

                //set the ClaimsPrincipal on the current thread.
                Thread.CurrentPrincipal = claimsPrincipal;

                // set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment.
                if (HttpContext.Current != null)
                {
                    HttpContext.Current.User = claimsPrincipal;
                }            

                // Verify that required permission is set in the scope claim
                if (ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value != "user_impersonation")
                {
                    return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.Unauthorized));
                }

                return base.SendAsync(request, cancellationToken);
            }
            catch (SecurityTokenValidationException)
            {
                return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.Unauthorized));
            }
            catch (Exception)
            {
                return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.InternalServerError));
            }
        }

When token validation succeeds, the claims from the token are used to set the caller’s identity on the current thread and HttpContext (both are required to ensure the context isn’t lost during transitions between ASP.NET and IIS). The claims will contain information about the user as well as the caller, which is the ToDo list client.

If token validation failed, an HTTP Unauthorized error is returned. Finally, before the method returns, the permissions that were in the token are checked. This can be done by checking the “scp” (scope) claim on the current claims principal. The required value is “user_impersonation” which allows the caller to create and read ToDo items.

                 // Verify that required permission is set in the scope claim
                if (ClaimsPrincipal.Current.FindFirst("scp").Value != "user_impersonation")
                {
                    return Task.FromResult<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.Unauthorized));
                }

                return base.SendAsync(request, cancellationToken);

The permissions granted to the ToDo list client are configured when the tenant admin consents to adding the client app to their tenant. This will be handled in a later step.

Now that you have learned how the TokenValidationHandler works, it needs to be registered in the Application_Start() method.

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
            GlobalConfiguration.Configuration.MessageHandlers.Add(new TokenValidationHandler());
        }

The last line in the snippet above adds the handler into the request pipeline for each incoming request.

Next, we will add a model for the ToDo items. This will be a small class that contains the ToDo Item data.

  1. In Solution Explorer, right-click on the Models folder, click Add, and then click Class.



    Add a new class

  2. In the Add New Item dialog, enter the name ToDoItem and click Add.



    Add new class

  3. The new class has been added to the project. Replace the default code with the following:

    namespace ToDoListService.Models
    {
        public class ToDoItem
        {
            public string Title { get; set; }
            public string Owner { get; set; }
        }
    }
    
    

  1. In Solution Explorer, right-click the Controllers folder, click Add, then click Controller.



    Add new controller

  2. In the Add Controller dialog, enter the name ToDoListController. From the Template drop-down menu, select API controller with empty read/write actions, and then click Add.



    Add controller

  3. Open ToDoListController.cs and replace the existing code with the following:

    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Web.Http;
    using ToDoListService.Models;
    
    namespace ToDoListService.Controllers
    {
        public class ToDoListController : ApiController
        {
            // ToDo items list for all users
            static ConcurrentBag<ToDoItem> todoBag = new ConcurrentBag<ToDoItem>();
    
            // GET api/todolist returns 
            public IEnumerable<ToDoItem> Get()
            {
                Claim subject = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier);
    
                return from todo in todoBag
                       where todo.Owner == subject.Value
                       select todo;
            }
    
            // POST api/todolist
            public void Post(ToDoItem todo)
            {
                if (null != todo && !string.IsNullOrWhiteSpace(todo.Title))
                {
                    todoBag.Add(new ToDoItem { Title = todo.Title, Owner = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value });
                }
            }
        }
    }
    
    Now we will discuss the code you’ve added. The ConcurrentBag object is a thread-safe object to save ToDo items for all users. In a real service these items would be saved in a database, but for simplicity we will just store the data in memory.

    // Todo items list for all users
    static ConcurrentBag<ToDoItem> todoBag = new ConcurrentBag<ToDoItem>();
    
    Now that we have a place to store ToDo items, the Post() method is updated to include the functionality to add items to the ConcurrentBag.

    // POST api/todolist
    public void Post(ToDoItem todo)
    {
        if (null != todo && !string.IsNullOrWhiteSpace(todo.Title))
        {
            todoBag.Add(new ToDoItem { Title = todo.Title, Owner = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value });
        }
    }
    
    Note the method signature has been updated to accept a ToDoItem object, which will serialize a form POST from a caller to the object. Additionally the method ensures that a ToDoItem won’t be added if it has an empty title.

    A new ToDoItem is created and saved to the ToDo list. The owner of the item is set to a subject identifier that is guaranteed to be unique for the user. ClaimsPrincipal in this method was previously set in Global.asax.cs and reflects the identity of the user in the token from Azure AD. As a result, this ensures that a user can only access their own ToDo items.

    The object id, or oid claim type that is being used to verify the user identity, is the object id of the user’s account in Azure AD.

    Next, we will discuss the Get() method:

            // GET api/todolist returns 
            public IEnumerable<ToDoItem> Get()
            {
                Claim subject = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier);
    
                return from todo in todoBag
                       where todo.Owner == subject.Value
                       select todo;
            }
    
    
    The signature is updated to return IEnumerable<ToDoItem>, which will be set to the ToDo items that the current user owns.

    The identity of the current user is determined by looking at the current ClaimsPrincipal, then returning only items that user own.

  4. Press F5 to verify that the service runs.

Now that we have a Web API written we can write the client application. This application will be the ToDo List Client that shows a user their ToDo items as well as allowing them to add new items.

We will begin writing the client, but switch to registering the client and service in you Azure AD tenant. This is needed in order to get a client id from Azure AD that will be used for the ToDo List Client.

The image below shows the application we will be building, which allows a user to grant authorization to the ToDo List Client to call the service, and add or view tasks.

To Do List Application

The steps for building the app will be:

  1. Create the project for the Windows Store Application

  2. Write the user interface in XAML

  3. In the code behind, use AAL to authenticate to the Web API

  4. Add an option to remove the account associated with the app so another user can view their ToDo items

Before beginning writing you application you will need to get a Windows 8 developer license, if you do not already have one. This is a free license, and can be acquired following these steps.

  1. In Visual Studio, create a new project in your existing solution. Right-click the solution in Solution Explorer, click Add, and then click New Project.

  2. From the Add New Project dialog, select Windows Store from the Visual C# drop-down on the left, and then select Blank App (XAML).



    Add new project



    Add new project

  3. Name the project “TodoListClient” and click OK.

You will need to install the Azure Authentication Library (AAL) for Windows Store NuGet Package for your ToDo List Client project. Open the NuGet Package Manager and search online for “AAL”, then select the AAL for Windows Store Package.

In order for AAL to be able to work in the ToDo List Client, the following capabilities must be granted to the application.

  • Enterprise Authentication

  • Internet (Client)

  • Private Networks (Client & Server)

  • Shared User Certificates

These can be set by opening the Package.appxmanifest file.

Open the Package.appxmanifest file

Then navigate to the Capabilities tab and select the required capabilities:

Select the required capabilities

When the ToDo List Client calls AAL to acquire a token, AAL may need to open the Windows Web Authentication Broker in order for a user to authenticate to AAD and grant access to the ToDo List Client to call the service. The Web Authentication Broker is used to render sign-in web pages in Windows Store applications.

The Web Authentication Broker will close when a web page hosted in the broker navigates to the callback URI that is registered with the broker. The default callback URI can be found by calling WebAuthenticationBroker.GetCurrentApplicationCallbackUri(). The URI contains the app’s SID, and looks like the following: ms-app://s-1-15-2-1482697178-942858973-646103894-873360561-3829487091-4151517581-178579545/

This will be the URI Azure AD uses to respond to OAuth 2.0 requests from the ToDo List Client, so must be registered in Azure AD to prevent the response from being returned to the wrong application.

  1. Add the following using directive to the MainPage.xaml.cs file: using Windows.Security.Authentication.Web;

  2. Add the following code to the OnNavigatedTo() method in MainPage.xaml.cs so that the method looks like the following:

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        string redirectUri = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString();
    }
    
  3. Set a breakpoint on this line you just added and run the ToDo List Client project:



    OnNavigatedTo method

  4. After the breakpoint is hit, step into the next line (F11) and save the value of redirectUri. This is the ToDo List Client’s redirect URI and will be needed to register the client with Azure AD.

    noteNote
    The ToDo List Client app URL is required to properly configure the service principal for the app in Azure AD. For example: ms-app://s-1-15-2-1482697178-942858973-646103894-873360561-3829487091-4151517581-178579545/

At this point, you have created the ToDo List Web API project and the ToDo List Client. Now they need to be registered with Azure AD by using the Azure Management Portal. The result of this registration process is that the ToDo List Client is authorized to call the ToDo List Web API on behalf of a user of the client. At a deeper level, the client is authorized to call the web API because after a user of the client authenticates with Azure AD, a token is returned that contains a scp claim with user_impersonation permissions.

  1. In a web browser, go to https://manage.windowsazure.com.

  2. In the Azure Management Portal, click on the Active Directory icon on the left menu, and then click on your organization’s name.

  3. From the Quick Start menu, click on the Applications tab.

  4. On the command bar, click Add.

  5. On the first page of the wizard, enter a user friendly name for your web API in the Name field. This name will enable it to be easily identified when looking at registered client applications in your Azure AD directory, so in our case, name it ToDo List Web API. The Web Application and/or Web API option under Type should already be selected, so click the arrow to proceed.

  6. On the next page, enter in the information for the web API’s App URL and App ID URI. The App URL is the address for the web API, and the App ID URI is the logical identifier for the app. In our case, both of these values are the same, so set them to http://localhost:29062/, and then click the next arrow.

  7. On the next page, select the directory access type that your web API needs. In our case, we are not accessing the Graph API, so you should select Single Sign-On, and then click the checkmark button to finish.

    After you have added your web API, you will need to register your client application. The following steps will show you how to complete this process.

  8. In the Azure Management Portal, go back to the Applications tab of your organization’s directory.

  9. On the command bar, click Add.

  10. On the first page of the wizard, enter a user friendly name for your client application, such as ToDo List Client and then select the Native Client Application option under Type. Click the arrow to proceed.

  11. On the next page, enter the Redirect URI for your client application. This is the location where Azure AD will redirect in response to an OAuth 2.0 request. This is where you will use the ms-app://… value obtained in the previous section, such as ms-app://s-1-15-2-1482697178-942858973-646103894-873360561-3829487091-4151517581-178579545/. Click the checkmark button.

    Your client application has now been added to Azure AD. The following steps will show you how to configure the client application’s access to the web API.

  12. The client application’s Quick Start page should be displayed. Click Configure on the top menu, and the application configuration page will appear.

  13. On the web apis section of the configuration page, expand the drop-down menu for Configure Web API Access for this Native Client Application, and then click the ToDo List Web API you previously added.

  14. After selecting the service, click Save on the command bar.

Now we will continue building the ToDo List Client.

The first piece of the client we’ll write is the user interface for the ToDo List Client.

Open the MainPage.xaml.cs file and replace the default markup with the following:

<Page
    x:Class="ToDoListClient.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ToDoListClient"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="120"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="120"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Column="1" Grid.Row="1" Style="{StaticResource PageHeaderTextStyle}" Text="ToDo List"/>
        <Grid Grid.Column="1" Grid.Row="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="150"/>
                <ColumnDefinition Width="300" />
            </Grid.ColumnDefinitions>
            <TextBox x:Name="ToDoText" Grid.Column="0" Grid.Row="2"  Margin="5" />
            <Button Grid.Column="1" Margin="10,0,0,0" Click="Button_Click_Add_Todo" >Add ToDo Item</Button>
            <HyperlinkButton Grid.Column="2" HorizontalAlignment="Right" Content="Remove Account" Click="HyperlinkButton_Click_Remove_Account"/>
        </Grid>
        <GridView x:Name="ToDoList" Grid.Column="1" Grid.Row="3" Margin="0,10,0,0" >
            <GridView.ItemTemplate>
                <DataTemplate>
                    <StackPanel Width="200" Height="150" Margin="10" Background="#FFA2A2A4" >
                        <TextBlock Text="{Binding Title}" FontSize="24" TextWrapping="Wrap" Margin="10"/>
                    </StackPanel>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>
    </Grid>
</Page>

This provides a simple Grid layout within a Grid, with three columns and four rows.

  • The <TextBox> is where the user enters the title of their ToDo item.

  • The <Button> is used to submit the ToDo items.

  • The <HyperlinkButton> is used to remove user session data.

  • The <GridView> is used to display all of the user’s ToDo items. The code-behind file will data bind the ToDo list to the GridView.

That completes the markup for creating the UI. Next we will add the code-behind file.

Now we will add the code-behind file to allow adding and getting ToDo items using the Web API. The client code will use AAL to request an authorization token from Azure AD in order to access the service.

Open the MainPage.xaml.cs file and add the below code.

using Microsoft.Preview.WindowsAzure.ActiveDirectory.Authentication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Windows.Data.Json;
using Windows.Security.Authentication.Web;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace TodoListClient
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        //Tenant name
        const string domainName = "[Your Domain Name. Example: contoso.onmicrosoft.com]";

        //Client app information
        const string clientID = "[Your client id. Example: de119d9a-c0b9-4ac0-b794-ca82e9d7dcd4]";

        //Resource information
        const string resourceAppIDUri = "[Your service application Uri. Example: http://localhost:29062/]";
const string resourceBaseAddress = "[The base address at which your service can be reached. Example: http://localhost:29062/]";

        private HttpClient httpClient = new HttpClient();

        private AuthenticationContext authenticationContext = new AuthenticationContext("https://login.windows.net/" + domainName);

        public MainPage()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            GetTodoList();
        }

        // Get the current user's todo items
        private async void GetTodoList()
        {
            HttpResponseMessage response;

            // Acquire a token from AAL. AAL will cache the authorization state in a persistent cache.
            AuthenticationResult result = await authenticationContext.AcquireTokenAsync(resourceAppIDUri, clientID);

            // Verify that an access token was successfully acquired
            if (AuthenticationStatus.Succeeded != result.Status)
            {
                DisplayErrorWhenAcquireTokenFails(result);
                return;
            }

            // Once the token has been returned by AAL, add it to the http authorization header, before making the call to access
            // the resoruce service.
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

            // Call the todolist web api
            response = await httpClient.GetAsync(resourceBaseAddress + "/api/todolist");

            // Verify the call to the todo list service succeeded
            if (response.IsSuccessStatusCode)
            {
                // Read the response as a Json Array and databind to the GridView to display todo items
                var todoArray = JsonArray.Parse(await response.Content.ReadAsStringAsync());

                TodoList.ItemsSource = from todo in todoArray
                                       select new
                                       {
                                           Title = todo.GetObject()["Title"].GetString()
                                       };
            }
            else
            {
                DisplayErrorWhenCallingResourceServiceFails(response.StatusCode);
            }
        }

        // Add a new todo item
        private async void Button_Click_Add_Todo(object sender, RoutedEventArgs e)
        {
            // Acquire a token from AAL. AAL will cache the authorization state
            AuthenticationResult result = await authenticationContext.AcquireTokenAsync(resourceAppIDUri, clientID);

            // Verify that an access token was successfully acquired
            if (AuthenticationStatus.Succeeded != result.Status)
            {
                DisplayErrorWhenAcquireTokenFails(result);
                return;
            }

            // Once the token has been returned by AAL, add it to the http authorization header, before making the call to access
            // the resoruce service.
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

            // Forms encode todo item, to POST to the todo list web api
            HttpContent content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("Title", TodoText.Text) });

            // Call the todolist web api
            var response = await httpClient.PostAsync(resourceBaseAddress + "/api/todolist", content);

            if (response.IsSuccessStatusCode)
            {
                TodoText.Text = "";
                GetTodoList();
            }
            else
            {
                DisplayErrorWhenCallingResourceServiceFails(response.StatusCode);
            }
        }

        // Clear the authorization state in the application
        private void HyperlinkButton_Click_Remove_Account(object sender, RoutedEventArgs e)
        {
            // Clear session state from the token cache
            (AuthenticationContext.TokenCache as DefaultTokenCache).Clear();

            // Reset UI elements
            TodoList.ItemsSource = null;
            TodoText.Text = "";
        }

        // Displays appropriate error when acquiring a token fails
        private async void DisplayErrorWhenAcquireTokenFails(AuthenticationResult result)
        {
            MessageDialog dialog;

            switch (result.Error)
            {
                case "authentication_canceled":
                    // User cancelled, no need to display a message
                    break;
                case "temporarily_unavailable":
                case "server_error":
                    dialog = new MessageDialog("Please retry the operation. If the error continues, please contact your administrator.", "Sorry, an error has occurred.");
                    await dialog.ShowAsync();
                    break;
                default:
                    // An error occurred when acquiring a token, show the error description in a MessageDialog 
                    dialog = new MessageDialog(string.Format("If the error continues, please contact your administrator.\n\nError: {0}\n\nError Description:\n\n{1}", result.Error, result.ErrorDescription), "Sorry, an error has occurred.");
                    await dialog.ShowAsync();
                    break;
            }
        }

        // Displays appropriate error when calling a resource service fails
        private async void DisplayErrorWhenCallingResourceServiceFails(HttpStatusCode statuscode)
        {
            MessageDialog dialog;

            switch (statuscode)
            {
                case HttpStatusCode.Unauthorized:

                    // An unauthorized error occurred, indicating the security token provided did not satisfy the service requirements
                    // acquiring a new token may fix the issue, this requires clearing state in the AAL cache.
                    // 
                    dialog = new MessageDialog("Would you like to reset your connection? This may help fix the problem.", "Sorry, accessing your todo list has hit a problem.");

                    dialog.Commands.Add(new UICommand("Yes", (c) =>
                    {
                        (AuthenticationContext.TokenCache as DefaultTokenCache).Clear();
                        GetTodoList();
                    }));

                    dialog.Commands.Add(new UICommand("No"));

                    await dialog.ShowAsync();
                    break;

                default:
                    dialog = new MessageDialog("If the error continues, please contact your administrator.”, “Sorry, an error has occurred.");
                    await dialog.ShowAsync();
                    break;
            }
        }
    }
}
noteNote
Before running the above code you must set the following three fields:

  1. domainName: The domain name of your Azure AD tenant, such as contoso.onmicrosoft.com

  2. clientID: The Client ID created when you registered the client with Azure AD, such as 3b616fda-41f9-47cc-8f88-35481e93c2dc. You can get this value from the application’s configuration page in the Azure Management Portal.

  3. resourceAppIDUri: The App ID URI for the ToDo List Web API. The standard practice is to set this to the same value as the service base address, such as http://localhost:29062/.

  4. resourceBaseAddress: The base address for the ToDo List Web API. This value must match the base address where your service is hosted, such as http://localhost:29062/.

Next we will discuss the code added to the code-behind. First, the following using directives were added to the top of MainPage.xaml.cs:

using Microsoft.Preview.WindowsAzure.ActiveDirectory.Authentication;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using Windows.Data.Json;
using Windows.Security.Authentication.Web;
using Windows.UI.Popups;

Then the values that are solution-specific for the ToDo List Client and ToDo List Service were added. Please see the previous section for a description of these values.

public sealed partial class MainPage : Page
{
    //Domain Name
    const string domainName = "[Your Domain Name. Example: contoso.onmicrosoft.com]";

    //Client app information
    const string clientID = "[Your client id. Example: de119d9a-c0b9-4ac0-b794-ca82e9d7dcd4]";

    //Resource information
    const string resourceAppIDUri = "[Your service application URI. Example: http://localhost:29062/]";
    const string resourceBaseAddress = "[The base address at which your service can be reached. Example: http://localhost:29062/]";

A private instance field for an HttpClient was added to make calls to the ToDo List Service: private HttpClient httpClient;

Then another private instance field for an AuthenticationContext was added, which is an AAL class. This class is configured with your tenant identifier. It directs AAL to request authorization for the correct Azure AD tenant: private AuthenticationContext authenticationContext = new AuthenticationContext("https://login.windows.net/" + domainName);

Now look at the GetToDoList() method. This method makes the call to the ToDo List Service to retrieve the ToDo items. In order to authenticate to the service, there is a call to request a token using the AAL AuthenticationContext class.

The App ID URI for the ToDo List Service is passed as the first parameter. The second parameter is the clientID of the ToDo List Client, which identifies the caller in the token request.

This is an asynchronous call, so the await keyword is used to block the thread until the call returns. This requires the GetTodoList() method to use the async modifier.

        // Get the current user's todo items
        private async void GetTodoList()
        {
            HttpResponseMessage response;

            // Acquire a token from AAL. AAL will cache the authorization state in a persistent cache.
            AuthenticationResult result = await authenticationContext.AcquireTokenAsync(resourceAppIDUri, clientID);

When the call to AcquireTokenAsync() is made, different things may occur. On first use, this call will cause the Windows Web Authentication Broker to open and show the Azure AD sign in page. This is shown below:

Login to organizational account

After the user has signed in once, the ToDo List Client will be granted access to call the service on behalf of the user who authenticated to Azure AD. This results in a refresh token being returned to the client from Azure AD.

The AAL library caches the refresh token returned from Azure AD. Then if the refresh token is in the cache, the call to AcquireToken() will not need to re-prompt the user for a token.

When the AAL uses the refresh token to request access tokens for the client, the user’s and client’s identities are encoded in the security token issued for the client to authenticate to the service. This allows the service to authorize the call on both the identities.

After the AcquireToken() call returns, the result is checked to verify the call succeeded:

    // Verify that an access token was successfully acquired
    if (AuthenticationStatus.Succeeded != result.Status)
    {
    DisplayErrorWhenAcquireTokenFails(result);
    return;
    }

If the call falls there is a call to display an error, using the AuthorizationStatus returned by the call. DisplayErrorWhenAcquireTokenFails() is used to perform the error checking and displays a message to the user when needed. The body of the method is shown below.

        // Displays appropriate error when acquiring a token fails
        private async void DisplayErrorWhenAcquireTokenFails(AuthenticationResult result)
        {
            MessageDialog dialog;

            switch (result.Error)
            {
                case "authentication_canceled":
// User cancelled, no need to display a message
                    break;
                case "temporarily_unavailable":
                case "server_error":
                    dialog = new MessageDialog("Please retry the operation. If the error continues, please contact your administrator.", "Sorry, an error has occurred.");
                    await dialog.ShowAsync();
                    break;
                default:
                    // An error occurred when acquiring a token, show the error description in a MessageDialog 
                    dialog = new MessageDialog(string.Format("If the error continues, please contact your administrator.\n\nError: {0}\n\n Error Description: {1}",result.Error, result.ErrorDescription), "Sorry, an error has occurred.");
                    await dialog.ShowAsync();
                    break;
            }
        }

There are three categories of errors handled:

  • User cancelled: The “authentication_canceled” error is returned when a user has canceled out of the authentication dialog. Since the user initiated this action, there is no need to display a message to them.

  • Retry-able error: When either "temporarily_unavailable" or "server_error" is returned, a transient issue at the Azure AD service may have occurred, due to high load or some other cause. The user may retry the operation.

  • Other: Then there is everything else, these are usually due to configuration issues and retrying the action is unlikely to fix the problem.

Now, going back to the GetTodoList() method, assuming no error occurred, and a token has been returned by AAL, either out of the AAL cache or a new token from Azure AD. Then the token is used to set the authorization header on the request to the ToDo List Service. When setting the authorization header, the token is prepended with “Bearer”, as required by RFC 6750 “The OAuth 2.0 Authorization Framework: Bearer Token Usage”, which defines how to access a resource with an OAuth 2.0 Bearer token.

// Once the token has been returned by AAL, add it to the http authorization header, before making the call to access
// the resource service.
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

An HTTP GET is then made to the ToDo List Service Web API, to get the list of ToDo items for the user.

// Call the todolist web api
response = await httpClient.GetAsync(resourceBaseAddress + "/api/todolist");

If the call succeeds, using a Linq query the JSON array returned by the ToDo List Service is used to construct a collection that can be used as the source of the data in the UI’s GridView object. This will automatically populate the GridView using data binding. The GridView ItemTemplate defined in MainPage.xaml binds a text box to the Title property of the ToDo items returned by the service.

// Verify the call to the ToDo List Service succeeded
if (response.IsSuccessStatusCode)
{
    // Read the response as a Json Array and databind to the GridView to display todo items
    var todoArray = JsonArray.Parse(await response.Content.ReadAsStringAsync());

    TodoList.ItemsSource = from todo in todoArray
                                       select new
                                       {
                                           Title = todo.GetObject()["Title"].GetString()
                                       };
}
else
{
    DisplayErrorWhenCallingResourceServiceFails(response.StatusCode);
}

If the web API call fails, DisplayErrorWhenCallingResourceServiceFails() is called to display a message to the user. Error handling addresses the special cases where an unauthorized error is returned. The ToDo List Service will handle this error when the token used for accessing the service is rejected. This includes cases where they token may have expired (though this is unlikely since AAL will request a new token prior to the token expiring), or the token does not pass the validation check for some other reason. In this case the user is presented with an option to reinitiate acquiring token, by having the code clear the AAL cache.

Additional cases may be added based on the behavior of the service being called.

        // Displays appropriate error when calling a resource service fails
        private async void DisplayErrorWhenCallingResourceServiceFails(HttpStatusCode statuscode)
        {
            MessageDialog dialog;

            switch (statuscode)
            {
                case HttpStatusCode.Unauthorized:

                    // An unauthorized error occurred, indicating the security token provided did not satisfy the service requirements
                    // acquiring a new token may fix the issue, this requires clearing state in the AAL cache.
                    // 
                    dialog = new MessageDialog("Would you like to reset your connection? This may help fix the problem.", "Sorry, accessing your todo list has hit a problem.");

                    dialog.Commands.Add(new UICommand("Yes", (c) =>
                    {
                        (AuthenticationContext.TokenCache as DefaultTokenCache).Clear();
                        GetTodoList();
                    }));

                    dialog.Commands.Add(new UICommand("No"));

                    await dialog.ShowAsync();
                    break;

                default:
                    dialog = new MessageDialog("If the error continues, please contact your administrator.”, “Sorry, an error has occurred.");
                    await dialog.ShowAsync();
                    break;
            }
        }

Next we will look at the code that adds new ToDo items to a user’s ToDo List. The method is very similar to the GetTodoList() method call. A token is acquired using AAL’s AuthenticationContext class. If AAL has already cached a refresh token, there will be no UI prompt. The token is then used to authenticate to the ToDo List Service, and a POST request is sent to the ToDo List Service with a forms encoded message containing the ToDo item. The ToDo item is then added to the user’s ToDo List at the service.

        // Add a new todo item
        private async void Button_Click_Add_Todo(object sender, RoutedEventArgs e)
        {
            // Aquire a token from AAL. AAL will cache the authorization state
            AuthenticationResult result = await authenticationContext.AcquireTokenAsync(resourceAppIDUri, clientID);

            // Verify that an access token was successfully acquired
            if (AuthenticationStatus.Succeeded != result.Status)
            {
                DisplayErrorWhenAcquireTokenFails(result);
                return;
            }

            // Once the token has been returned by AAL, add it to the http authorization header, before making the call to access
            // the resoruce service.
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

            // Forms encode todo item, to POST to the todolist web api
            HttpContent content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("Title", TodoText.Text) });

            // Call the todolist web api
            var response = await httpClient.PostAsync(resourceBaseAddress + "/api/todolist", content);

            if (response.IsSuccessStatusCode)
            {
                TodoText.Text = "";
                GetTodoList();
            }
            else
            {
                DisplayErrorWhenCallingResourceServiceFails(response.StatusCode);
            }
        }

This method is called by the Add ToDo button via a click event in the UI:

<Button Grid.Column="1" Margin="10,0,0,0" Click="Button_Click_Add_Todo" >Add ToDo</Button>

Now a user can grant access to the ToDo List Client to call the ToDo List Service, and view and add items to their list. Next we will look at the code that to clears the refresh token from the client. This will clear the AAL cache and allow a different user to use the ToDo List Client. The token cache can be set to a custom implementation; by default it is set to a cache of type DefaultTokenCache.

  // Clear the authorization state in the application
private void HyperlinkButton_Click_Remove_Account(object sender, RoutedEventArgs e)
{
    // Clear session state from the token cache
    (AuthenticationContext.TokenCache as DefaultTokenCache).Clear();

    // Reset UI elements
    TodoList.ItemsSource = null;
    TodoText.Text = "";
}

The DefaultTokenCache is provided as part of AAL and is the default token cache used by AuthenticationContext. The DefaultTokenCache will cache tokens base on the Azure AAD tenant, resource and client, so there may be multiple refresh tokens and access tokens in the cache.

If the DefaultTokenCache is used in an app that is running to a Windows 8 device that is connected to a Microsoft Account, the contents of the cache will roam between machines that are connected to the same account.

The above code accesses the AuthenticationContext TokenCache, and clears all authorization state. This event handler must be wired to the click event in the UI:

<HyperlinkButton Grid.Column="2" HorizontalAlignment="Right" Content="Remove Account" Click="HyperlinkButton_Click_Remove_Account"/>

This completes the ToDo List Client code. The service and client are now ready to test.

Now let’s test the ToDo List Client. Make sure the client and service are both running. When the client launches you will see the below page. With the initial call to AAL, the Web Authentication Broker will open, and the user enters their credentials into the Azure AD sign-in page.

Login to organizational account

The user can now enter an item to their ToDo List by typing a title and clicking the Add ToDo Item button.

Todo List Application first run

When the button is clicked, AAL will be called again, but this time a cached token will be returned and used to call the ToDo List Service. The item is added and a list of the user’s ToDo items are returned.

Todo List Application first run

To try the ToDo Client as a different user, sign in to your Azure AD tenant. If you created your directory within an Azure subscription, go to the Azure Management Portal and sign in using your tenant adminstrator account.

On the Active Directory tab select your directory.

On the bottom toolbar, select Add User, to create a new user.

Add a user

A dialog will appear that walks through creating a user.

Add new user

Once the account is added, go back to the ToDo List Client and click the Remove Account link. This will clear user state from the client.

Now when the Add ToDo Item button is clicked, and the call is made to AAL, the user will be prompted to authenticate. This time use your new user account.

Login to the app

After autheniticating, you can manage the new user’s ToDo List.

The completed app

By switching between the user accounts you can test that the service is returning the correct items for each user.

Now you have completed building the ToDo List application.

In this optional section we will discuss the steps needed to deploy your ToDo List Service to an Azure Web Site. If you have not previously created an Azure Web Site, please refer to this article to get started. Once you have named the URL for the web site please stop and follow the next step to update the audience in the service.

Since the hostname changes for the service, we will also update the app ID URI for the service. As an example we’ll use todolistservice.azurewebsites.net as the Azure Web Site. This will require a change to the TodoListService project. Open the Global.asax.cs in the project and update the audience to match the URL of your Azure Web Site. For example, update the audience to be the new resource App ID URI:

const string audience = "https://todolistservice.azurewebsites.net";

With that change to the service you are ready to deploy.

Since the resource, ToDo List Service, has a new app ID URI after being deployed to Azure, the client needs to be updated to due to this change. The client will also need to be updated to use the new URL.

In the MainPage.xaml.cs update the resource constants to use the new hosting address, for example:

const string resourceAppIDUri = "https://todolistservice.azurewebsites.net";
const string resourceBaseAddress = "https://todolistservice.azurewebsites.net";

The client will now connect to the correct URL for the service and request access tokens intended for this service from AAL.

As a final step, a service registration will be added for the instance of the Azure-hosted ToDo List Service running in the Azure Web Site. The ToDo List Client must also have permission granted to it to call the Azure hosted service.

To make this configuration change, we need to update the web API’s information in the Azure Management Portal.

  1. In a web browser, go to https://manage.windowsazure.com.

  2. In the Azure Management Portal, click on the Active Directory icon on the left menu, and then click on your desired directory.

  3. From the directory’s Quick Start page, click on the Applications tab near the top of the page.

  4. Click on the ToDo List Service from the list of registered applications.

  5. From the application’s Quick Start page, click on the Configure tab near the top of the page.

  6. In the section for single sign-on, update the App ID URI and the Reply URL with the http://todolistservice.azurewebsites.net address, and then click Save on the command bar.

Once you’ve made these changes, run the ToDo List Client and save a few ToDo items to the Azure Web Site to try it out. Note, that since the ToDo items are stored in memory of the service, as the service is recycled, the items will disappear.

You’ve now completed this walkthrough. For more information about the technologies used in this walkthrough, see the related topics below. Additionally, the finished product of this walkthrough closely resembles the AAL for Windows Store code sample, which is available on the MSDN Code Gallery: Code Sample: Windows Store Application to REST Service - Authentication with AAD via Browser Dialog

See Also

Community Additions

Show:
© 2014 Microsoft