Creating An Azure API To Generate a Bearer Token Against Azure AD Using User Assigned Managed Identity

Introduction 

 
In our earlier article, we explained a custom API for fetching the key vault secrets that were built using Azure API Management Gateway and Azure Functions to provide an endpoint for doing the operation. In this blog, we are going to create another endpoint for generating a new Azure Active Directory BearerToken using a managed identity assigned to Azure Function. This API will be using the same architecture as described in the earlier article.
 
Ok, so now that we have laid the groundwork, let’s begin by understanding why we need this? The first thing that any developer will have to figure out for performing any operations (CRUD) in a controlled Azure environment is a way to authenticate the application with Azure to get the required token, even though Microsoft has provided many custom solutions for authentication which can be implemented in custom applications to retrieve access tokens.
 
One of the most commonly used authentication approaches is a service principle-based approach where we would create a service principal in Azure Active Directory and then assign required permissions on APIs against which the access token is to be retrieved. After the service principal is created, we will write the authentication module using the created service principal client ID, client secret, and resource URI of the API on which permissions were granted in Azure Active Directory. There are a few problems with this approach:
  • Service Principle details need to be shared with all applications or we will need to create separate service principles for each application
  • Password rotation policies become extremely tricky to apply in multi-usage scenarios
  • Additional overhead if we have to do this at more than one place and more than one module/application as it will need to rewrite those solutions at each place.
  • To comply with security standards, we will have to move service principle details to the key vault and then implement a module (we have done that using API in an earlier blog) to fetch it and use in the authentication module
This is where this particular API comes into the picture. This custom API will take care of the authentication module and can be reused. With this custom API, we will:
  • No longer need the service principle as we will be using User Assigned Managed Identity
  • Not rewrite of the authentication-related code in every module/functionality
  • Provide a single end-point to generate bearer token for any given resource URI, like https://management.azure.com, https://vault.azure.net, etc. to use respective Azure REST APIs
  • Use a generated bearer token to perform any kind of operations in Azure using Azure REST APIs of resource Uri against which token is generated
  • Use generated bearer token to invoke REST APIs from any platform
As stated above, we are creating this API on the same lines as our previous API so all pre-requisites are applicable here with additional pre-requisite i.e. User Assigned Managed Identity. We have to create a User Assigned Managed Identity in Azure and need to add the same at Azure Function App created in the previous article. Let’s do it by following the below steps.
  • Go to the function app (created in the above-mentioned article) in Azure Portal
  • Navigate to Identity option available under the Settings section
  • Click on “user assigned” tab and click on Add button
  • Now, on the displayed form, select the subscription under which we have created our user-assigned managed identity, search that identity in the search box and select it.
  • Now, click on the Add button.
 Creating An Azure API To generate Bearer Token against Azure AD Using User Assigned Managed Identity
 
Once this is done, created identity should be granted with Contribute/Owner access at subscription/resource group under which resources are to be accessed, updated, created, or deleted in Azure.
 
Note
For details on Managed Identities, please go through this article.
 
Now that we are ready with all pre-requisites, let’s jump right to writing API code.
 
Open the master solution created in an earlier article, in VS code, and create a new Azure Function Project with C# as the language, "GenerateBearerToken" as a function name, HTTP Trigger as the Function Template and Authorization level as Anonymous/Function (this is required for API Management). This will create a class file with the name “GenerateBearerToken.cs”.
 
Now, lets code the Azure Function to generate Bearer Token against Azure Active Directory using User Assigned Managed Identity. Open GenerateBearerToken.cs file and update the below code in it.
  1. using System;  
  2. using System.IO;  
  3. using System.Threading.Tasks;  
  4. using Microsoft.AspNetCore.Mvc;  
  5. using Microsoft.Azure.WebJobs;  
  6. using Microsoft.Azure.WebJobs.Extensions.Http;  
  7. using Microsoft.AspNetCore.Http;  
  8. using Microsoft.Extensions.Logging;  
  9. using Newtonsoft.Json;  
  10.   
  11. namespace Your-Namespace  
  12. {  
  13.     public static class GenerateBearerToken  
  14.     {  
  15.         [FunctionName("GenerateBearerToken")]  
  16.         public static async Task<IActionResult> Run(  
  17.             [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,  
  18.             ILogger log)  
  19.         {  
  20.             string token = string.Empty;  
  21.             log.LogInformation("CreateBearerToken Function is called");  
  22.   
  23.             try  
  24.             {  
  25.                 string resourceUri = req.Query["ResourceUri"];  
  26.                 string requestBody = await new StreamReader(req.Body).ReadToEndAsync();  
  27.                 dynamic data = JsonConvert.DeserializeObject(requestBody);  
  28.                 resourceUri = resourceUri ?? data?.ResourceUri;  
  29.   
  30.                 if (!string.IsNullOrEmpty(resourceUri))  
  31.                 {  
  32.                     log.LogInformation("Fetching details from KeyVault");  
  33.                     //Fetching UAMI Client Id and Tenant Id from Key Vaults  
  34.                     string clientId_UAMI = await KeyVaultHelper.FetchKeyVaultSecret(ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.clientId_UAMI), log);  
  35.                     string tenantId = await KeyVaultHelper.FetchKeyVaultSecret(ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.tenantId), log);  
  36.                     token = await TokenHelper.GetToken(clientId_UAMI, tenantId, resourceUri, log);  
  37.                       
  38.   
  39.                     if (!string.IsNullOrEmpty(token))  
  40.                     {  
  41.                         return new OkObjectResult("Bearer " + token);  
  42.                     }  
  43.                     else  
  44.                     {  
  45.                         return new OkObjectResult("[Error] Exception has been occured in generating token.Please check Function logs under Monitor");  
  46.   
  47.                     }  
  48.                 }  
  49.                 else  
  50.                 {  
  51.                     return new BadRequestObjectResult("[Warning] Resource Uri is missing in request");  
  52.                 }  
  53.   
  54.             }  
  55.             catch (Exception ex)  
  56.             {  
  57.                 return new NotFoundObjectResult($"\n GenerateBearerToken got an exception \n Time: { DateTime.Now} \n Exception{ ex.Message}");  
  58.   
  59.             }  
  60.         }  
  61.     }  
  62. }  
Now, create two helper classes – one with the name “KeyVaultHelper.cs” and another one with the name “TokenHelper.cs” and update the below code in them respectively.
 
KeyVaultHelper.cs
  1. using Microsoft.Extensions.Logging;  
  2. using Newtonsoft.Json.Linq;  
  3. using System;  
  4. using System.Collections.Generic;  
  5. using System.Net.Http;  
  6. using System.Text;  
  7. using System.Threading.Tasks;  
  8.   
  9. namespace Your-Namespace  
  10. {  
  11.     public class KeyVaultHelper  
  12.     {  
  13.   
  14.         /// <summary>  
  15.         /// To fetch secret from Key Vault  
  16.         /// </summary>  
  17.         /// <param name="secretName"></param>  
  18.         /// <param name="log"></param>  
  19.         /// <returns></returns>  
  20.         public static async Task<string> FetchKeyVaultSecret(string secretName, ILogger log)  
  21.         {  
  22.             string value = string.Empty;  
  23.             try  
  24.             {  
  25.                 using (var client = new HttpClient())  
  26.                 {  
  27.                     //Invoking FetchSecretFromKeyVaultAPI to fetch secret value  
  28.                     string Uri = ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.FetchSecretFromKeyVaultAPI) + "?SecretName=" + secretName;  
  29.                       
  30.                     //Adding subscription key header to the request  
  31.                     client.DefaultRequestHeaders.Add(ConstantsHelper.ocp_Apim_Subscription_Key, ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.ocp_Apim_Subscription_Key));  
  32.                       
  33.                     //Get response  
  34.                     var response = await client.GetAsync(Uri).ConfigureAwait(false);  
  35.                       
  36.                     if (response.StatusCode == System.Net.HttpStatusCode.OK)  
  37.                     {  
  38.                         value = await response.Content.ReadAsStringAsync();  
  39.                         return value;  
  40.                     }  
  41.                     else  
  42.                     {  
  43.                         log.LogInformation("FetchKeyVaultSecret is failed with status code : " + response.StatusCode);  
  44.                         return value;  
  45.                     }  
  46.                 }  
  47.   
  48.             }  
  49.             catch (Exception ex)  
  50.             {  
  51.                 log.LogInformation($"CreateBearerTokenV3 got \n Exception Time: {DateTime.Now} \n Exception{ ex.Message}");  
  52.                 return value;  
  53.             }  
  54.             }  
  55.     }  
  56. }  
In this KeyVaultHelper.cs file, we are using custom API that was built in an earlier article to fetch secrets from the key vault. Here, we are fetching Client Id of created User Assigned Managed Identity and Azure Tenant Id which we have stored in the key vault to avoid the security breaches.
 
Like we did earlier, we used environmental variables in our code, we did the same here. So now we would need to update them at Azure Function App settings. In this Azure Function, we used the below variables as environmental variables and their corresponding values will be fetched from app settings at run time. We need to update them in ConstantsHelper.cs created in an earlier article.
  1. //GenerateBearerToken Constants  
  2.         public const string tenantId = "tenantId";  
  3.         public const string clientId_UAMI = "clientId_UAMI";  
  4.         public const string ocp_Apim_Subscription_Key = "Ocp-Apim-Subscription-Key";  
  5.         public static string FetchSecretFromKeyVaultAPI = "FetchSecretFromKeyVaultAPI";  
Lets update these variables at Function App level by following the below steps:
  • Go to Azure Function App in Azure portal
  • Click on Configuration under settings section
  • Click on new application settings
  • Provide name as "tenantId" and value as "your-secret-name-in-key-vault"
  • Repeat the above steps for the other three variables.
TokenHelper.cs
  1. using System;  
  2. using System.Threading.Tasks;  
  3. using Microsoft.Extensions.Logging;  
  4. using Microsoft.Azure.Services.AppAuthentication;  
  5. using System.Net.Http;  
  6.   
  7. namespace Your-Namespace  
  8. {  
  9.     public class TokenHelper  
  10.     {  
  11.   
  12.         /// <summary>  
  13.         /// To fetch bearer token   
  14.         /// </summary>  
  15.         /// <param name="clientID"></param>  
  16.         /// <param name="tenantID"></param>  
  17.         /// <param name="resourceUri"></param>  
  18.         /// <param name="log"></param>  
  19.         /// <returns></returns>  
  20.         public static async Task<string> GetToken(string clientID, string tenantID, string resourceUri, ILogger log)  
  21.         {  
  22.             string accessToken = string.Empty;  
  23.             try  
  24.             {  
  25.                 var connectionString = "RunAs=App;AppId=" + clientID + "; TenantId="+tenantID+"";  
  26.                 var azureServiceTokenProviderUAMI = new AzureServiceTokenProvider(connectionString);  
  27.                 accessToken = await azureServiceTokenProviderUAMI.GetAccessTokenAsync(resourceUri);  
  28.                 System.Threading.Thread.Sleep(2000);  
  29.                 return accessToken;  
  30.             }  
  31.             catch (Exception e)  
  32.             {  
  33.                 log.LogInformation($"GetToken got Exception \n  Time: {DateTime.Now} \n Exception{e.Message}");  
  34.                 return accessToken;  
  35.             }  
  36.               
  37.         }  
  38.     }  
  39. }  
Now that we are ready with our new function, let's deploy this to the Azure Function App resource that we created as part of our earlier article.
  • In Visual Studio Code, select F1 to open the command palette. In the command palette, search for and select Azure Functions: Deploy to function app
  • If you're not signed in, you're prompted to Sign in to Azure. After you sign in from the browser, go back to Visual Studio Code. If you have multiple subscriptions, select a subscription that contains your function app.
  • Select your existing function app in Azure. When you're warned about overwriting all files in the function app, select Deploy to acknowledge the warning and continue
Once the deployment is done, we will see a new function along with the previous function at the function app.
 
Creating An Azure API To generate Bearer Token against Azure AD Using User Assigned Managed Identity 
 
Next, we will import the deployed function into the Azure API Management service that was created as part of an earlier article, by following the below steps.
  • Go to the created API Management Service Instance
  • Select API option under APIs section and select the existing API imported earlier
  • Now, click on the contextual menu (…) present against selected API
  • Click on Import option on the displayed dialog box
 Creating An Azure API To generate Bearer Token against Azure AD Using User Assigned Managed Identity
  • On the next screen, click on the Function App as API type to Import.
Creating An Azure API To generate Bearer Token against Azure AD Using User Assigned Managed Identity
 
Now, a model dialog will be displayed to Import API from Function App where we have to click on the Browse button.
 
Creating An Azure API To generate Bearer Token against Azure AD Using User Assigned Managed Identity 
  • Now, on the next screen select function app, as soon as we select the function app, it will display all available functions under the selected function app.
  • Select only the “GenerateBearerToken” function and click on select.
Note
Please do not select the “GetKeyVaultSecret” function as this was imported previously otherwise a duplicate operation will be created under API.
 
Creating An Azure API To generate Bearer Token against Azure AD Using User Assigned Managed Identity 
  • Once the import is completed, we will see the GenerateBearerToken operation added under API along with the GetKeyVaultSecret operation.
Now we are all set to invoke the API from any custom application, postman, or any other platform to generate a new Azure Active Directory Bearer Token for any given resource Uri, using managed identity assigned. Let's test it out directly from API Management Story in Azure Portal by following below steps.
  • Go to the created API Management Service Instance
  • Select the API option under APIs section
  • Select the API that we created under All APIs section and it will display the "GenerateBearerToken" operation of selected API with its respective exposed method i.e. Get.
  • Click on "GenerateBearerToken" operation and click on Test tab
  • Add a Query Parameter using key and value i.e. "ResourceUri" and "Resource Uri of Azure Service e.g. https://management.azure.com, against which bearer token is needed"
Creating An Azure API To generate Bearer Token against Azure AD Using User Assigned Managed Identity
  • Now, Click on Send to get a response with generated bearer token for requested resource Uri.
 Creating An Azure API To generate Bearer Token against Azure AD Using User Assigned Managed Identity
 
Like we said in an earlier article, to invoke a configured API from outside of API Management Services, we need to pass Ocp-Apim-Subscription-Key as a header, for the subscription key of the product that is associated with this API. For more details on Subscriptions, please read this article.
 
Below is a sample code with C# Restsharp using Postman:
  1. var client = new RestClient("https://your-apim-site.azure-api.net/GenerateBearerToken?ResourceUri=resource-uri-of-azure-service-against-which-bearer-token-is-required");    
  2. var request = new RestRequest(Method.GET);    
  3. request.AddHeader("Ocp-Apim-Subscription-Key""your-subscription-key");    
  4. IRestResponse response = client.Execute(request);    
We now have a fully functional endpoint that allows us to generate a new Azure Active Directory Bearer Token for any given resource URI, using a managed identity assigned by writing only an invoke-command from your application which keeps the functionality loosely coupled, independently deployable and highly maintainable. The entire code base for the Reusable API solution is available at Reusable APIs.
 
All the best! Happy Coding...