Call Multiple Services With One Login Prompt Using ADAL

This blog will show how to create a client application using Active Directory Authentication Library (ADAL) that authenticates to multiple Web API applications in Azure Active Directory while only prompting the user a single time for credentials.

Background

I wrote a previous post that showed how you can create your own Web API that enables impersonation of the calling user, allowing you to call a different web service on behalf of the current user.  I used Office 365 APIs as a demonstration, but that same approach works with other types of services such as Graph API or your own Web API.

image

While this is an awesome feature (I am still excited that this is now so easy to do), what if we want the client to call the resource endpoints directly without requiring a custom Web API?  Yes, we can.

image

Turns out this is incredibly easy to do, especially if you are using Visual Studio 2013 Update 2 RC.  We will create this astonishingly dreary-looking application that, while it is in desperate need of attention by someone who can design pretty user interfaces, will demonstrate an incredibly cool concept using Azure Active Directory.

image

Refresh Tokens for Multiple Resources

Azure Active Directory provides a feature called multi-resource refresh tokens.  The concept is simple, you can use a single refresh token to request access tokens for multiple resources.

For more information, see Refresh Tokens for Multiple Resources.

The Active Directory Authentication Library (ADAL) library is smart enough to see if a refresh token is available.  If a refresh token is available, it will present that refresh token to Azure AD and receive an access token without requiring an additional authentication prompt.  This is possible because of the support in Azure AD for multi-resource refresh tokens. 

The trick to making this work with the ADAL library is to create a single AuthenticationContext and reuse that for all secured calls. 

Let’s try it out to see how it works.

Create a Web API Endpoint

Create a new ASP.NET Web Application.  Choose the template as Web API, and change the authentication to Organizational Account. 

image

Notice the App ID URI.  That’s also known as the resource, that’s the identifier of the thing in Azure AD that you are going to call.  We’ll use that value in our client app in a few minutes. 

Sign in as a global administrator (allowing Visual Studio to automatically register the app for you in Azure AD).  Now click the “create remote resources” option to automatically create an Azure web site.  This is a new feature in Visual Studio 2013 Update 2 RC.

image

I am prompted for a site name and Azure subscription.

image

Choose OK, and Visual Studio will automatically register 2 applications in Azure AD: the one running on localhost, used for local debugging, and one for your new Azure Web Site.  You can now right-click your ASP.NET Web API project and choose Publish.  Go to the Settings tab and enable organizational authentication, providing your domain.

image

Click Publish, and your Web API is now published to a new Azure Web Site, secured with Azure AD.

Update Web API Permissions

Go to the Azure Management Portal.  Go to your directory and choose the Applications tab.  Find your new Web API application.

image

Open it and choose Configure.  At the bottom of the page there is a “Manage Manifest” button.  Click it and choose “Download Manifest”.  Save the file locally, then edit it with Visual Studio 2013 Update 2 RC, which provides color-coded JSON editing.  By default it will have an appPermissions property with an empty array.

image

We will replace that property with the following:

Sample Code

  1. "appPermissions": [
  2.     {
  3.       "claimValue": "user_impersonation",
  4.       "description": "Allow full access to the AADWebAPI service on behalf of the signed-in user",
  5.       "directAccessGrantTypes": [],
  6.       "displayName": "Have full access to the AADWebAPI service",
  7.       "impersonationAccessGrantTypes": [
  8.         {
  9.           "impersonated": "User",
  10.           "impersonator": "Application"
  11.         }
  12.       ],
  13.       "isDisabled": false,
  14.       "origin": "Application",
  15.       "permissionId": "b69ee3c9-c40d-4f2a-ac80-961cd1534e40",
  16.       "resourceScopeType": "Personal",
  17.       "userConsentDescription": "Allow full access to the AADWebAPI service on your behalf",
  18.       "userConsentDisplayName": "Have full access to the AADWebAPI service"
  19.     }
  20.   ],

The final result looks like this:

image

Save the file locally.  Go back to the Azure Management Portal.  Go to the application again and choose Manage Manifest, then Upload Manifest.  Upload the file.

Note: If you do this enough times, you are going to have a bunch of .json files with GUIDs in your Downloads folder. By default, the file will have the same name as the client ID for your application.

We are now ready to create an app to consume our Web API endpoint.

Create a Windows App

Add a Windows App client application using the Blank App template.  When the app is created, open MainPage.xaml and add a textbox named “txtCallback”. 

image

In the MainPage method, add a call to find out the callback URI for this app.

Sample Code

  1. public MainPage()
  2. {
  3.     this.InitializeComponent();
  4.     txtCallback.Text = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString();
  5. }

Run the app, you’ll see the callback URI for the app that starts with ms-app.

image

Go to the Azure Management Portal, navigate to your directory, and then go to the Applications tab.  Click the Add button at the bottom of the page.  Choose “Add an application my organization is developing.”

image

Give the app a name (it can be quite helpful to use the same name as the app here.

image

In the next screen, copy the callback URI from your Windows 8 app and paste it into the screen.

image

Once the app is created, go to the Configure tab and go to the “Permissions to Other Applications” section.  By default the application is granted permission to call the Graph API.  Add permission to read directory data.

image

Since we’ve configured the permissions, the dropdown will now show our Web API with the ability to add permission for this client to call that API.  Enable that permission.

image

Finally, add permission to call the O365 SharePoint Online API to read items in all site collections.

image

MAKE SURE YOU HIT SAVE!  I have forgotten this several times, and you’ll debug and see that you get a null access token.  If you see this, it means you failed to hit save here.

Finally, go to the “Update Your Code” section to obtain the client ID.

image

Create the UI

I am terrible at making user interfaces.  Always have been, and this time is no exception.  Here is the XAML for my application.

Sample Code

  1. <Page x:Class="AADClient.MainPage"
  2.       xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.       xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  4.       xmlns:local="using:AADClient"
  5.       xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
  6.       xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
  7.       mc:Ignorable="d">
  8.  
  9.     <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
  10.                 Orientation="Vertical">
  11.         <TextBox TextWrapping="Wrap"
  12.                  Text="TextBox"
  13.                  VerticalAlignment="Top"
  14.                  Width="660"
  15.                  x:Name="txtCallback"
  16.                  Margin="0,100,0,0" />
  17.         <Button Content="Call APIs"
  18.                 Click="Button_Click"
  19.                 Height="50"
  20.                 Width="110"
  21.                 Margin="400,0,0,0" />
  22.         <Button Content="Logout"
  23.                 Height="50"
  24.                 Width="110"
  25.                 Click="Button_Click_1"
  26.                 Margin="400,0,0,0" />
  27.         <StackPanel Orientation="Horizontal"
  28.                     Height="Auto"
  29.                     Margin="0,50,0,0">
  30.             <TextBlock Width="400"
  31.                        Text="Web API"
  32.                        FontSize="36">
  33.             </TextBlock>
  34.             <TextBlock Width="400"
  35.                        Text="SharePoint Online"
  36.                        FontSize="36"></TextBlock>
  37.             <TextBlock Width="400"
  38.                        Text="Graph API"
  39.                        FontSize="36"></TextBlock>
  40.         </StackPanel>
  41.         <StackPanel Orientation="Horizontal"
  42.                     Height="Auto"
  43.                     Margin="0,50,0,0"
  44.                     MinHeight="100">
  45.             <TextBlock x:Name="webAPIResult"
  46.                        Width="400"
  47.                        TextWrapping="Wrap"></TextBlock>
  48.             <TextBlock x:Name="spoResult"
  49.                        Width="400"
  50.                        TextWrapping="Wrap"></TextBlock>
  51.             <TextBlock x:Name="graphResult"
  52.                        Width="400"
  53.                        TextWrapping="Wrap"></TextBlock>
  54.         </StackPanel>
  55.     </StackPanel>
  56. </Page>

The main part to call out is that there are 3 text blocks that show the data from each of the API calls as a raw string. 

Finally, Some Code!

The app is registered to use Azure Active Directory, now we have to add the reference to the ADAL library.  Right-click the project and choose Manage NuGet Packages, search for “adal”, and include the prerelease option to take advantage of new features in the ADAL library.  Install it.

image

Add two buttons to MainPage.xaml.  The first one will be used to call the endpoints.  The second will be used to log out.  The code for MainPage.xaml is pretty straightforward.

We add using statements:

Sample Code

  1. using Microsoft.IdentityModel.Clients.ActiveDirectory;
  2. using System;
  3. using System.Net.Http;
  4. using System.Runtime.InteropServices;
  5. using System.Threading.Tasks;
  6. using Windows.Security.Authentication.Web;
  7. using Windows.UI.Xaml;
  8. using Windows.UI.Xaml.Controls;

Add a few fields to our class.

Sample Code

  1. //Context used for all operations
  2. AuthenticationContext context;
  3. //The domain used to authenticate
  4. string aadDomain = "kirke.onmicrosoft.com";
  5. //The client ID of the native app
  6. string clientAppClientID = "309a37cd-4246-4a78-b5b2-ee453cecd9ee";

Now, we add a method to call Azure AD to obtain an access token, then call an API.

Sample Code

  1. /// <summary>
  2. /// Obtains an access token from Azure AD
  3. /// and then calls the API endpoint
  4. /// </summary>
  5. /// <param name="APIEndpoint">API to call</param>
  6. /// <param name="AppIDURI">The APP ID Uri of the
  7. /// API to call.  Value is found on the Configure
  8. /// tab of the Azure Management Portal.</param>
  9. /// <returns></returns>
  10. private async Task<string> GetResult(
  11.     string APIEndpoint,
  12.     string AppIDURI)
  13. {
  14.     AuthenticationResult ar =
  15.         await context.AcquireTokenAsync(
  16.             AppIDURI,
  17.             clientAppClientID);
  18.     string authHeader =
  19.         ar.CreateAuthorizationHeader();
  20.  
  21.     //Create an HTTP client for the local web API
  22.     var client = new HttpClient();
  23.     var request = new HttpRequestMessage(
  24.         HttpMethod.Get,
  25.         APIEndpoint);
  26.  
  27.     request.Headers.TryAddWithoutValidation(
  28.         "Authorization",
  29.         authHeader);
  30.  
  31.     var response = await
  32.         client.SendAsync(request);
  33.     var responseString = await
  34.         response.Content.ReadAsStringAsync();
  35.  
  36.     return responseString;
  37. }

When the button is clicked, we will form the URLs to our three different services in the same directory. 

Sample Code

  1. private async void Button_Click(object sender, RoutedEventArgs e)
  2. {
  3.     string authority = string.Format(
  4.         "https://login.windows.net/{0}",
  5.         aadDomain);
  6.  
  7.     //One AuthenticationContext for the whole domain
  8.     //  and all operations.  
  9.     context = new AuthenticationContext(authority);
  10.  
  11.     //Call our Web API endpoint
  12.     string APIEndpoint = "https://aadwebapi.azurewebsites.net/api/values";
  13.     string APIAppIDURI = "https://kirke.onmicrosoft.com/WebApp-aadwebapi.azurewebsites.net";
  14.  
  15.     string response = await GetResult(
  16.         APIEndpoint,
  17.         APIAppIDURI);
  18.  
  19.     webAPIResult.Text = response;
  20.  
  21.     //Call SharePoint Online
  22.     APIEndpoint = "https://kirke.sharepoint.com/sites/dev/_api/Web?$select=Title";
  23.     APIAppIDURI = "https://kirke.sharepoint.com";
  24.  
  25.     response = await GetResult(
  26.         APIEndpoint,
  27.         APIAppIDURI);
  28.  
  29.     spoResult.Text = response;
  30.  
  31.     //Call Graph API
  32.     APIEndpoint = "https://graph.windows.net/kirke.onmicrosoft.com/me?api-version=2013-11-08";
  33.     APIAppIDURI = "https://graph.windows.net";
  34.  
  35.     response = await GetResult(
  36.         APIEndpoint,
  37.         APIAppIDURI);
  38.  
  39.     graphResult.Text = response;
  40.        
  41. }

Finally, we add the code to log out. 

Sample Code

  1. private void Button_Click_1(object sender, RoutedEventArgs e)
  2. {
  3.     context.TokenCacheStore.Clear();
  4.     // Also clear cookies from the browser control.
  5.     ClearCookies();
  6.  
  7. }
  8.  
  9. private void ClearCookies()
  10. {
  11.     const int INTERNET_OPTION_END_BROWSER_SESSION = 42;
  12.     InternetSetOption(IntPtr.Zero,
  13.         INTERNET_OPTION_END_BROWSER_SESSION,
  14.         IntPtr.Zero,
  15.         0);
  16. }
  17.  
  18.  
  19. [DllImport("wininet.dll", SetLastError = true)]
  20. private static extern bool InternetSetOption(IntPtr hInternet,
  21.     int dwOption,
  22.     IntPtr lpBuffer,
  23.     int lpdwBufferLength);
  24.     }
  25. }

The Big Payoff

Think about how cool this is… with a bit of configuration and an incredibly small amount of code, we have called 3 different secure services and will only have 1 login prompt.  We run the app and click the Call APIs button.  When the first call to AcquireTokenAsync occurs, we see the login page.

image

Once we log in, we see the data from each of our API calls without further prompts.

image

I am incredibly excited about Azure AD and the ADAL library.  This opens up so many scenarios while reducing the amount of security stuff you have to know in order to build a secure application. 

For More Information

Refresh Tokens for Multiple Resources 

Windows Azure Active Directory Graph

Calling O365 APIs from your Web API on behalf of a user

Secure ASP.NET Web API with Windows Azure AD