There are a few methods to secure API’s on Azure’s API Management platform, and the one we are going to explore is using OAuth 2.0 Client Credential Grant. This will allow us to require an OAuth token (in the Authorization HTTP Header) on every request that is then pre-validated before the request is forwarded to the backend service.

Fortunately there is a step by step guide Protect an API by using OAuth 2.0 with Azure Active Directory and API Management by Microsoft on how to do this, and rather than repeat its steps I will explain some parts that were not overly obvious to me.

What is OAuth 2.0 Client Credential Grant?

OAuth provides for a few authentication flows, and Client Credential Grant is ideal for system to system calls that are not acting as a particular user. A good guide to the different flows is Alex Bilbie’s A Guide to OAuth 2.0 Grants.

How do I get a OAuth 2.0 Token?

You get a OAuth Token as the client application by requesting a token from the token endpoint (in our case hosted by Azure Active Directory). You pass in the Azure Active Directory Id, client id, client secret, and uniquely to Azure the resource we are trying to access (the app id of the API we are accessing). A request looks like the following (with id’s changed):

POST https://login.microsoftonline.com/2a6144c5-0aab-4aba-3ds2-3cavebd8afef/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: login.microsoftonline.com
Content-Length: 183
Expect: 100-continue
Connection: Keep-Alive

client_id=4ffassfg-asfg-4fff-b0f1-5f24a5456vca&client_secret=rdMjDRagvadfGasffttjWMg2fgqZgaaahfEg9zCVFEM%3D&grant_type=client_credentials&resource=ef12ba51-a14a-4a3f-af34-57b4945773b6

First I tried to request a token using Postman however it’s built in UI for getting token’s does not allow for the resource parameter, and the token I received was for audience 00000002-0000-0000-c000-000000000000 which is the Graph API.

To request a token from code I spent sometime looking to use an existing library that support OAuth 2.0, however I found them overly complex as they handle all flows. So I have created the following class instead:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CityWorksApiTest
{
    public class AzureApiSecurityHandler : DelegatingHandler
    {
        private String clientId = ConfigurationManager.AppSettings["CleintId"];
        private String clientSecret = ConfigurationManager.AppSettings["ClientSecret"];
        private String accessingResource = ConfigurationManager.AppSettings["ApiResourceId"];
        private String subscriptionKey = ConfigurationManager.AppSettings["SubscriptionKey"];
        private String tokenEndpointUrl = ConfigurationManager.AppSettings["TokenEndpointUrl"];

        private HttpClient tokenClient = new HttpClient();
        private String currentToken = null;

        public AzureApiSecurityHandler(HttpMessageHandler innerHandler) : base(innerHandler)
        {
        }
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            request.Headers.Add("Authorization", GetTokenAsync().Result);
            request.Headers.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
            var response = await base.SendAsync(request, cancellationToken);

            // Token may have expired, fetch a new one on first 401
            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                currentToken = null;
                request.Headers.Remove("Authorization");
                request.Headers.Add("Authorization", GetTokenAsync().Result);
                return await base.SendAsync(request, cancellationToken);
            }
            return response;
        }
        protected async Task<string> GetTokenAsync()
        {
            if (currentToken != null)
            {
                return currentToken;
            }
            var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpointUrl);
            request.Content = new FormUrlEncodedContent(new Dictionary<string, string> {
                { "client_id", clientId },
                { "client_secret", clientSecret },
                { "grant_type", "client_credentials" },
                { "resource", accessingResource }
            });
            var response = await tokenClient.SendAsync(request);
            response.EnsureSuccessStatusCode();
            var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
            currentToken = payload.Value<string>("access_token");
            return currentToken;
        }
    }
}

What does an OAuth Token Look Like?

Once you have a token you can view it using jwt.io, simply paste it into the site.

{
  "aud": "ef12ba51-a14a-4a3f-af34-57b4945773b6",
  "iss": "https://sts.windows.net/XXX/",
  "iat": 1536716908,
  "nbf": 1536716908,
  "exp": 1536720808,
  "aio": "XXX",
  "appid": "4ffassfg-asfg-4fff-b0f1-5f24a5456vca",
  "appidacr": "1",
  "idp": "https://sts.windows.net/XXX/",
  "oid": "XXX",
  "roles": [
    "Api.Invoke.All"
  ],
  "sub": "XXX",
  "tid": "XXX",
  "uti": "XXX",
  "ver": "1.0"
}
The interesting parts of this are:
  • “aud’ is the audience, and is the application id of the API we are accessing. This comes from the resource parameter on the request.
  • “appid” is the application id of the client, and comes from the client_id parameter.
  • “roles” are the permissions the client has been granted.

The token also includes a signature, and the part I found interesting during my research was that the server can cache the signing certificate meaning that when validating each request it will not always call back to the token issuer.

What are Azure Active Directory Applications?

Azure Active Directory has the concept of Applications (listed under App Registrations) which are used to represent each of the applications involved. A good article to understand them more is Azure Active Directory application model.
The way I have setup my system is to have 3 applications representing:
  1. The API
  2. The Developer Portal client
  3. My client application
In addition to the steps in the guide I have created an application role on the API (1) by modifying its manifest and adding an appRoles. This allows us to have different access for different clients, and also, to be more explicit in what access we are granting:

"appRoles":[  
   {  
      "allowedMemberTypes":[  
         "Application",

      ],
      "displayName":"TestApi.Invoke.All",
      "id":"1b4f816e-5eaf-48b9-8613-7923830595ad",
      "isEnabled":true,
      "description":"Invoke all operations",
      "value":"TestApi.Invoke.All"
   }
],

A good guide on how to do this is Defining permission scopes and roles offered by an app in Azure AD.

I have then granted the new permissions to the client applications.

How is the OAuth Token Pre-Authorised?

As per the guide we can validate the JWT token via a policy. The policy I am using is as follows:

        <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
            <!-- URL contains the aad tennant name -->
            <openid-config url="https://login.microsoftonline.com/AAD_TENNANT_NAME/.well-known/openid-configuration" />
            <audiences>
                <audience>{{ApiAppId}}</audience>
            </audiences>
            <required-claims>
                <claim name="roles" match="any" separator="">
                    <value>Api.Invoke.All</value>
                </claim>
            </required-claims>
        </validate-jwt>
The key parts of the policy are:
  • Specify the configuration url by adding your Active Directory tenant name
  • Specify the Application Id of the API. I used a Named value as it will be different in each environment.
  • Specify the required role(s).

How do the clients know how to get a token?

There are steps in the guide to add an OAuth 2.0 configuration, this is so that the developer portal knows how to request a token for the Try It feature. Initial I thought this was how you secure the API, however it later became clear this is only client configuration. These steps also affect the swagger for the API by adding to its securityDefinition element as follows.

        <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
            <!-- URL contains the aad tennant name -->
            <openid-config url="https://login.microsoftonline.com/AAD_TENNANT_NAME/.well-known/openid-configuration" />
            <audiences>
                <audience>{{ApiAppId}}</audience>
            </audiences>
            <required-claims>
                <claim name="roles" match="any" separator="">
                    <value>Api.Invoke.All</value>
                </claim>
            </required-claims>
        </validate-jwt>