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.
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.
- using System;
- using System.IO;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.Azure.WebJobs;
- using Microsoft.Azure.WebJobs.Extensions.Http;
- using Microsoft.AspNetCore.Http;
- using Microsoft.Extensions.Logging;
- using Newtonsoft.Json;
-
- namespace Your-Namespace
- {
- public static class GenerateBearerToken
- {
- [FunctionName("GenerateBearerToken")]
- public static async Task<IActionResult> Run(
- [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
- ILogger log)
- {
- string token = string.Empty;
- log.LogInformation("CreateBearerToken Function is called");
-
- try
- {
- string resourceUri = req.Query["ResourceUri"];
- string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
- dynamic data = JsonConvert.DeserializeObject(requestBody);
- resourceUri = resourceUri ?? data?.ResourceUri;
-
- if (!string.IsNullOrEmpty(resourceUri))
- {
- log.LogInformation("Fetching details from KeyVault");
-
- string clientId_UAMI = await KeyVaultHelper.FetchKeyVaultSecret(ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.clientId_UAMI), log);
- string tenantId = await KeyVaultHelper.FetchKeyVaultSecret(ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.tenantId), log);
- token = await TokenHelper.GetToken(clientId_UAMI, tenantId, resourceUri, log);
-
-
- if (!string.IsNullOrEmpty(token))
- {
- return new OkObjectResult("Bearer " + token);
- }
- else
- {
- return new OkObjectResult("[Error] Exception has been occured in generating token.Please check Function logs under Monitor");
-
- }
- }
- else
- {
- return new BadRequestObjectResult("[Warning] Resource Uri is missing in request");
- }
-
- }
- catch (Exception ex)
- {
- return new NotFoundObjectResult($"\n GenerateBearerToken got an exception \n Time: { DateTime.Now} \n Exception{ ex.Message}");
-
- }
- }
- }
- }
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
- using Microsoft.Extensions.Logging;
- using Newtonsoft.Json.Linq;
- using System;
- using System.Collections.Generic;
- using System.Net.Http;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace Your-Namespace
- {
- public class KeyVaultHelper
- {
-
-
-
-
-
-
-
- public static async Task<string> FetchKeyVaultSecret(string secretName, ILogger log)
- {
- string value = string.Empty;
- try
- {
- using (var client = new HttpClient())
- {
-
- string Uri = ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.FetchSecretFromKeyVaultAPI) + "?SecretName=" + secretName;
-
-
- client.DefaultRequestHeaders.Add(ConstantsHelper.ocp_Apim_Subscription_Key, ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.ocp_Apim_Subscription_Key));
-
-
- var response = await client.GetAsync(Uri).ConfigureAwait(false);
-
- if (response.StatusCode == System.Net.HttpStatusCode.OK)
- {
- value = await response.Content.ReadAsStringAsync();
- return value;
- }
- else
- {
- log.LogInformation("FetchKeyVaultSecret is failed with status code : " + response.StatusCode);
- return value;
- }
- }
-
- }
- catch (Exception ex)
- {
- log.LogInformation($"CreateBearerTokenV3 got \n Exception Time: {DateTime.Now} \n Exception{ ex.Message}");
- return value;
- }
- }
- }
- }
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.
-
- public const string tenantId = "tenantId";
- public const string clientId_UAMI = "clientId_UAMI";
- public const string ocp_Apim_Subscription_Key = "Ocp-Apim-Subscription-Key";
- 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
- using System;
- using System.Threading.Tasks;
- using Microsoft.Extensions.Logging;
- using Microsoft.Azure.Services.AppAuthentication;
- using System.Net.Http;
-
- namespace Your-Namespace
- {
- public class TokenHelper
- {
-
-
-
-
-
-
-
-
-
- public static async Task<string> GetToken(string clientID, string tenantID, string resourceUri, ILogger log)
- {
- string accessToken = string.Empty;
- try
- {
- var connectionString = "RunAs=App;AppId=" + clientID + "; TenantId="+tenantID+"";
- var azureServiceTokenProviderUAMI = new AzureServiceTokenProvider(connectionString);
- accessToken = await azureServiceTokenProviderUAMI.GetAccessTokenAsync(resourceUri);
- System.Threading.Thread.Sleep(2000);
- return accessToken;
- }
- catch (Exception e)
- {
- log.LogInformation($"GetToken got Exception \n Time: {DateTime.Now} \n Exception{e.Message}");
- return accessToken;
- }
-
- }
- }
- }
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.
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
- On the next screen, click on the Function App as API type to Import.
Now, a model dialog will be displayed to Import API from Function App where we have to click on the Browse button.
- 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.
- 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"
- Now, Click on Send to get a response with generated bearer token for requested resource Uri.
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:
- var client = new RestClient("https://your-apim-site.azure-api.net/GenerateBearerToken?ResourceUri=resource-uri-of-azure-service-against-which-bearer-token-is-required");
- var request = new RestRequest(Method.GET);
- request.AddHeader("Ocp-Apim-Subscription-Key", "your-subscription-key");
- 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...