Calling API Management from Azure Function using Managed Identities

One of the solutions I am consulting on today is securing a number of APIs with OAuth with client credential flow, using Azure Active Directory as the identity provider. Those APIs are exposed via Azure API Management, which makes the validation of the access tokens provided as simple as injecting a policy at a product, API or operation level.

While this all works nicely, there is one item in this process which is a painpoint from an operations point of view: as they use client certificates to request access tokens, the certificate management is becoming a bit of a chore with the number of APIs and clients that access those APIs increasing.

A lot of those clients are either Azure Functions or Azure Logic Apps, which today provide the ability to using a managed identity to easy the burden of maintaining credentials.

So it was only logical to think about how would we be able to use the managed identities configured in both Azure Functions and Logic Apps to generate a token that can be validated by API Management. Turns out that this is quite possible, but needs a bit of preparation…

Pre-Requisites

I already had an Azure Function in place and needed to integrate using managed identities. So, in order to get this working I needed do to 3 things:

  1. Register a new Azure AD Application, with at least one role.
  2. Create a managed identity and associate it to your Azure Function.
  3. Associate the managed identity with the Azure AD Application role.

Once this is setup, I could implement the code that would generate the JWT token and pass it to Azure API Management, which in turn could validate the token, securing the backend.

Register a new Azure AD Application

You can find the detailed instructions to Register a new Azure AD Application here. For your convenience here are the basic steps:

From the Azure Portal, navigate to Active Directory. From there:

  1. Click on Application Registrations
  2. Click on New Registration

On the new Registration form:

  1. Provide a name
  2. Select the types of supported accounts – if using this app registration exclusively to authenticate with managed identities, you can use the default option (Accounts in this organizational directory only)
  3. Provide a Redirect URI – in this specific scenario, any URL is valid, since I believe this value is not used in the authentication with managed identity scenario.
  4. After you complete the steps, the app registration is now ready to use.

There are a couple more steps to do to complete this process.

Expose the Registered App as an API

Find your newly created application registration and open it. From there:

  1. Click on expose an API
  2. Define the Application ID URI – this is any valid URI that you will use later on to identity your application.

Create a new role

Still from the App registration blade, follow the instructions below:

  1. Click on App Roles
  2. Click on Create app role
  3. Provide the display name – this is the string that will be showed in the App roles list)
  4. Allowed member types – I am selected Applications, as I only wanted this role used by the managed identity, but you can select either applications or both.
  5. Provide a Value – this is string that will be used later by the API Management to validate the token
  6. Provide a description
  7. Make sure you enable the role
  8. Click on apply

This will create a new Role that you can use in a later step to associate to the managed identity.

Create a Managed Identity

There are two ways to ways to create a managed identity. You can either create a managed identity and associate it to an Azure Function. You can either manually create a managed identity and associate it to the function, by associating an Azure Function to an User Assigned Managed Identity or let the system create one for you by associating an Azure Function to an System Assigned Managed Identity. In this post I will use a User Assigned Managed Identity, because this is what I needed on my example, and I didn’t find as many examples as this one (but I will try to make notes below on the difference).

Associating a Function with an User Assigned Managed Identity.

Create a new Managed Identity

To create your own managed identity, navigage to Managed Identities within the Azure Portal and follow the steps below:

  1. Click on Add
  2. Select the subscription where the User Assigned Managed Identity will be added
  3. Select the Resource Group where it will be deployed
  4. Select the Associated Region
  5. Provide the name of the Managed Identity (you will need the name to associate it with the Azure Functions in the next step).
  6. Click on Review and Create and once validations has passed, click on create.

Connect User Assigned Identity to an Azure Function

Within the Azure Functions you want to connect, follow the steps below:

  1. On the side blade, select Identity
  2. Within the Identity blade, select User assigned
  3. Click on Add
  4. Select the correct subscription
  5. Search and select the user assigned managed identity (using the name you gave in the previous step)
  6. Confirm that the identity was selected (it will move to Selected identities)
  7. Click on Add

Once you finish this, the new identity will show on the list of User Assigned Identities.

Things to notice:

  • You can have more than one User Assigned Identity associated to a function.
  • You can follow the same process above to add an user assigned identity to a Logic Apps or Web Apps – It will probably follow the same steps for any resource that exposes the interface above.
  • Creating a System assigned identity is as simple as clicking on System Assigned on that screen, setting the status to On and clicking on save. In this case, the name of the System Assigned Identity will be the same as the name of the resource you created (e.g. if your Azure Function is called MyFunction, the System Assigned Identity will be called MyFunction.

Associating the Managed Identity to the Application Role

To be able to request a token for the application setup on the first step, the managed identity create on step 2 needs to be given permission to access that application and have a role assigned. This is not something that can be done in the portal today. The script below can be used to assign a managed identity to a role:

 # Install the Azure AD module if you don't have it yer. (You need admin on the machine.)
 # Install-Module AzureAD
  
 $tenantID = '<tenantID guid>'
 $serverApplicationName = '<Application Registration Name>'
 $managedIdentityName = '<managed identity name - for system assigned is the name of your resource>'
 $appRoleName = '<role name>'
  
 Connect-AzureAD -TenantId $tenantID
  
 # Look up the web app's managed identity's object ID.
 $managedIdentity = (Get-AzureADServicePrincipal -Filter "DisplayName eq '$managedIdentityName'")
 $managedIdentityObjectId = $managedIdentity.ObjectId
  
 # Look up the details about the server app's service principal and app role.
 $serverServicePrincipal = (Get-AzureADServicePrincipal -Filter "DisplayName eq '$serverApplicationName'")
 $serverServicePrincipalObjectId = $serverServicePrincipal.ObjectId
 $appRoleId = ($serverServicePrincipal.AppRoles | Where-Object {$_.Value -eq $appRoleName }).Id
  
 # Assign the managed identity access to the app role.
 New-AzureADServiceAppRoleAssignment -ObjectId $managedIdentityObjectId  -Id $appRoleId -PrincipalId $managedIdentityObjectId -ResourceId $serverServicePrincipalObjectId 

The code above was adjusted from an original post on Microsoft Docs. For more information you can find it here.

Using the Managed Identity inside an Azure Function

Once all of this are setup, I could finally get the Azure Function to request an JWT token to the application in my tenant’s Azure Active Directory.

To do that, I took advantage of the App Authentication client library for .NET – that library allows you to get access tokens from a specific resource within Azure Active Directory, allowing you to define which identity you require to request that token.

The code is pretty simple, and looks like this:

 
 /*
  The following entries should be added to your Application Settings:
  TargetApp        - API URI of the application registration created
                     in step 1
  TargetTenantID   - The Tenant where the application registration and
                     managed identities where created was created
  FunctionIdentity - The connection string used to select the type
                     of managed identity
 */
 var target = Environment.GetEnvironmentVariable("TargetApp");
 var tenantID = Environment.GetEnvironmentVariable("TargetTentantID");
 var identity = Environment.GetEnvironmentVariable("FunctionIdentity");
  
 var azureServiceTokenProvider = new AzureServiceTokenProvider(identity);
 string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync(target,tenantID); 

Most of the items above are quite self explanatory. The FunctionIdentity environment variable, though, needs a bit of more information. The AzureServiceTokenProvider class can receive a connection-string-like parameter, which defines the type of identity you are using to connect. There are many permutations of those connections strings. For the purpose of this example, the one that must be used when running in Azure is:

 RunAs=App;AppId={ClientId of user-assigned identity}

ClientID in this case is the client ID of the user assigned identity associated with the function.

Other useful connection strings can be found in the table below:

Connection StringDescription
RunAs=Developer; DeveloperTool=AzureCliUsed for local development. Azure CLI (object ID 04b07795-8ddb-461a-bbee-02f9e1bf7b46 should be add as a client to the App Registration in order for development to work. See more details here.
RunAs=AppUsed for running in the cloud with System-Assigned Identity

With access to the JWT token, it is just a matter now of making an HTTP request to API Management passing the token. In my case, the code looked like:

 /*
 The following entries should be added to your Application Settings:
 ApimKey - the API Key associated to the API being called
 ApimUrl - the Url of the endpoint being called
 */
 var apiKey = Environment.GetEnvironmentVariable("ApimKey");
 var apiUrl = Environment.GetEnvironmentVariable("ApimUrl");

 var wc = new System.Net.Http.HttpClient();
 wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
 wc.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", apiKey);
 // In this case the call is a GET, so using GetAsync.
 var result = await wc.GetAsync(apiUrl);

In order to apply the same technique to Logic Apps, and invoke APIM using a managed identity, once you setup all the pre-requisites you just need to configure the Authentication of an HTTP action, selecting Managed Identities (1), the identity you want to use (2) and providing the API URI of the app registration you defined on the first step (3):

Finally, securing APIM

The last step of the puzzle was to make sure that APIM when receiving the call from Azure Functions would be able to validate the JWT token. That was something I did a couple of times before, and I was collecting pieces of the puzzle while creating the various App Registrations and roles.

To secure an API in APIM, I simply included the following validate-JWT policy in the Inbound rule of my API:

<!--
replace the following values with the values in your solution:
tenantID - the guid representing your Azure Active Directory Tenant ID
clientId - client ID of the Application registered on Step 1
roleID   - the value of the role defined on Step 1
-->
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid." require-scheme="Bearer">
     <openid-config url="https://login.microsoftonline.com/{{tenantID}}/v2.0/.well-known/openid-configuration" />
     <audiences>
         <audience>{{clientID}}</audience>
     </audiences>
     <issuers>
         <issuer>https://sts.windows.net/{{tenantID}}/</issuer>
     </issuers>
     <required-claims>
         <claim name="roles" match="any">
             <value>{{roleId}}</value>
         </claim>
     </required-claims>
</validate-jwt> 

But why all this trouble?

Although this looks like a lot of work, there is a big reason to go through all of this trouble – once you have this in place, you will have a passwordless solution for all your API internal calls. The project I was working on had a lot of integration between systems, and in order to get all of this secured, using the classic client credentials flow, we would have to manage lots of certificates or shared secrets. That means that we had to make sure that:

  • We stored all the secrets in a secure manner
  • We have a good handle on rotating those secrets on a regular basis to avoid applications breaking due to secrets expiring.

Using this technique we would let Azure manage the secrets for us, removing a big burden from the operational maintenance of the app.

Food for thought: user assigned x system assigned managed identities

I initially implemented this using system assigned, as the process to create system assigned identities is so simple and now the identity inherits the name of the resource being created, which makes it much easier to identify.

But think about it a bit more, I think user assigned identity in this case is a better option for the following reasons:

  • If you have multiple Function Apps that are logically the same application, you can give a single identity for all those apps.
  • On the other hand if in the same Function App you have different Functions that needs different roles, you can have multiple user assigned identities connected to the same function – you can for example have a reader Identity and a writer Identity and make sure that you use the right identity at the right time, instead of have one identity that does all.
  • If your application is deployed in multiple regions for redundancy, you don’t need to have each region creating its own identity. You can have a single identity that represents the actual application and centralize the permissions on that identity.

I couldn’t find any real guidance on the use of user x system managed identity on Microsoft Docs, but I will update this post if I find any. In the meantime, I am leaning towards user assigned identity because of the flexibility it seems to provide.


Sharing is caring...

Leave a Reply

Your email address will not be published. Required fields are marked *