Azure Active Directory Authentication (Easy Auth) with Multiple Backend APIs


This blog post is a continuation of a scenario that I discussed previously in my blog post on Azure Active Directory Authentication with Custom Backend Web API. I also expanded on that post with some notes on local debugging of apps using Easy Auth. If you didn't read those posts, you may want to start by browsing through them to make it easier to follow along.

I discussed how you can use the access token obtained in an Azure Web App using App Service Authentication (a.k.a. Easy Auth) to access a backend API. The basic principle is to use the resource=APP-REGISTRATION-ID-OR-URI parameter when authorizing with Azure Active Directory. For example, a call like (with additional parameters):

POST https://login.microsoftonline.com/TENANT-NAME.onmicrosoft.com/oauth2/authorize?resource=https://graph.microsoft.com&response_type=id_token code
 

would start the authentication flow and ultimately lead to the web application obtaining a token that can be used to access the Microsoft Graph API. App Service Authentication (Easy Auth) automates this for you and makes the tokens available to the application without the need for any authentication specific code in the application. In the previous post, we set the resource specification to be the ID of an Azure App Registration used to secure access to a custom backend API. At the end of walking through the scenario, I hinted at a potential problem. What happens if you would like to access multiple backend APIs. A given token can only be used with one API (unless they use the same app registration). In the following I will show that you can obtain tokens for any additional APIs using an on-behalf-of flow. As a specific example, I will demonstrate how to access a custom API (the List API from the previous example) and the Microsoft Graph API. The scenario would look something like this:

 

 

The Graph API could be used to access documents in the users Office 365 or look for information in Azure Active Directory. The link to Office 365 in the illustration is just an example.

The first step is to add Graph API to the required permissions of the app registration:

In this case, we have somewhat arbitrarily added 3 delegated permissions on the Graph API, but you can decide which are appropriate for your application. Please consult the previous blog post for details on how to set up the app registration and configuring App Service Authentication.

Next we will add functionality to the application to access the Graph API. You can access the updated application code on GitHub. Compared to the previous version of the application, I have introduced the EasyAuthProxy to make it easier to do local debugging of the code as described in a previous blog post. The functionality for accessing the Graph API can be found in GraphController.cs:

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using ListClientMVC.Models;
using ListClientMVC.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;

namespace ListClientMVC.Controllers
{
    public class GraphController : Controller
    { 

        private IEasyAuthProxy _easyAuthProxy;
        private IConfiguration _configuration;

        public GraphController(IEasyAuthProxy easyproxy, IConfiguration config) {
            _easyAuthProxy = easyproxy;
            _configuration = config;
        }

        public async Task<IActionResult> Index()
        {
            //Get a token for the Graph API
            string id_token = _easyAuthProxy.Headers["x-ms-token-aad-id-token"];
            string client_id = _configuration["AADClientID"];
            string client_secret = _configuration["AADClientSecret"];
            string aad_instance = _configuration["AADInstance"];

            var client = new HttpClient();

            var content = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
                new KeyValuePair<string, string>("assertion", id_token),
                new KeyValuePair<string, string>("requested_token_use", "on_behalf_of"),
                new KeyValuePair<string, string>("scope", "User.Read"),
                new KeyValuePair<string, string>("client_id", client_id),
                new KeyValuePair<string, string>("client_secret", client_secret),
                new KeyValuePair<string, string>("resource", "https://graph.microsoft.com"),
                                
            });

            var result = await client.PostAsync(aad_instance + "oauth2/token", content);
            string resultContent = await result.Content.ReadAsStringAsync();

            if (result.IsSuccessStatusCode) {
                //Call Graph API to get some information about the user
                TokenResponse tokenResponse= JsonConvert.DeserializeObject<TokenResponse>(resultContent);

                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.access_token);
                var response = await client.GetAsync("https://graph.microsoft.com/v1.0/me");
                var cont = await response.Content.ReadAsStringAsync();
                ViewData["me"] = cont;
            } else {
                ViewData["me"] = "Failed to access MS Graph";
            }

            return View();
        }
    }
}

For simplicity, I have added all the required code for getting a token and passing it to the Graph API in a single function. In a more practical application, you would probably add this functionality in some sort of service that may include some caching of tokens and manage refreshing of expiring tokens. The code goes through two phases: 1) a call to https://login.microsoftonline.com/TENANT.onmicrosoft.com/oauth2/token to obtain a token and 2) a call to the Graph API where the token is passed as a bearer token in an authorization header.

The first time the application is accessed, it will ask for delegated permissions. Compared to the previous application, the user is now asked for Graph permissions in addition to the List API:

After granting permissions, it is possible to access the updated version of the application:

As shown above, there are now a couple of new menu items, one for accessing the list through the backend List API and one for accessing the Microsoft Graph API.

And that is it, we have created an application that uses Easy Auth to authenticate the users. After authentication, the user can access a backend List API directly using the token obtained as part of the Easy Auth flow and the user will also be able to access the Microsoft Graph API by having the application obtain a token "on-behalf-of" the user.

If you prefer to handle all user authentication in your application code, you can use OpenID Connect. Please see this example for a very similar flow using OpenID Connect and a custom backend API. For completeness, I have implemented made another version of the ListClient, which uses OpenID Connect. Please refer to that code repository for details.

Let me know if you have questions/comments/suggestions.

Comments (0)

Skip to main content