Quote of the Day

more Quotes

Categories

Get notified of new posts

Buy me coffee

  • Home>
  • security>

Authenticate against azure ad using certificate in a client credentials flow

I have an API which needs to authenticate against azure ad to obtain an access token for calling another downstream API. When registering an application in azure AD for the caller API, I could either setup a shared secret or a certificate for the API to use as part of its credentials in a client credentials flow . In the past, I had always used a shared secret as it was more convenient and easier to setup. However, using certificate provides stronger security. After spending a few hours of googling and hacking, I was able to setup and use a certificate instead of a shared secret as credentials for the caller API to authenticate against azure AD.

Why using a certificate is more secure than a shared secret?

A shared secret is just like a plain text password. In an access token request, the client transmits the shared secret without any disguise as shown in the sample request below. Anyone with knowledge of the client id and the shared secret can impersonate the client. Below snippet shows a sample request taken from the document.

POST /{tenant}/oauth2/v2.0/token HTTP/1.1           //Line breaks for clarity
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

client_id=535fb089-9ff3-47b6-9bfb-4f1264799865
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_secret=sampleCredentia1s
&grant_type=client_credentials

On the other hand, when using a certificate, the client does not transmit the certificate. Rather, the client uses the certificate’s private key to sign the request. Azure AD validates the signature using the public key of the certificate. If the signature validation passes, azure AD knows the request must have been signed by the client which posses the certificate. Below snippet from the document shows an an access token request using a certificate.

POST /{tenant}/oauth2/v2.0/token HTTP/1.1               // Line breaks for clarity
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_id=97e0a5b7-d745-40b6-94fe-5f77d35c6e05
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJSUzI1NiIsIng1dCI6Imd4OHRHeXN5amNScUtqRlBuZDdSRnd2d1pJMCJ9.eyJ{a lot of characters here}M8U3bSUKKJDEg
&grant_type=client_credentials

In the above request, the client_assertion parameter is

An assertion (a JSON web token) that you need to create and sign with the certificate you registered as credentials for your application. Read about certificate credentials to learn how to register your certificate and the format of the assertion.

Access token request with a certificate

Setup a self-signed certificate using azure key vault

A certificate can come from different sources. For instance, you may store it in a Windows certificates stores, a location on hard drive, azure key vault etc … Azure key vault has built in support for storing and managing certificates. In addition, since my apps run on azure app services and connect to azure key vaults using managed identities, using azure key vault to store the certificate seems like a no brainer.

For info on how to create a self signed certificate to use in an application, checkout the document.

Upload the certificate to azure app registration

Once you have created the certificate, download the certificate in CER or PFX/PEM format. In my case, I downloaded the .CER file. The certificate file contains the public key info which I upload to the app registration of the caller API.

Upload a certificate in azure app registration

Code setup using Microsoft Authentication Library for .NET (MSAL.NET) and Azure Key Vault Certificate Client library for .NET

In the application, I use MSAL.NET to request an access token for the caller API. I just need to setup a IConfidentialClientApplication and use the API method AcquireTokenForClient to conveniently authenticate the client against azure AD and obtain an access token via the client credentials flow. I encapsulate all the logic of retrieving an access token in a class, as shown in the below code snippets.

using Azure.Identity;
using Azure.Security.KeyVault.Certificates;
using App.Core.Options;
using Microsoft.Extensions.Options;
using Microsoft.Identity.Client;
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;    

public class AzureTokenService 
    {
        private readonly IConfidentialClientApplication _confidentialClientApplication;
        private readonly AzureADOptions _azureADOptions;
        private readonly KeyVaultOptions _keyVaultOptions; 

        public AzureTokenService(IOptions<AzureADOptions> azureADOptions,
            IOptions<KeyVaultOptions> keyVaultOptions)
        {
            _azureADOptions = azureADOptions.Value;
            _keyVaultOptions = keyVaultOptions.Value;

            _confidentialClientApplication = ConfidentialClientApplicationBuilder.Create(_azureADOptions.ClientId)
                .WithCertificate(GetCertificate())
                .WithAuthority(_azureADOptions.Authority).Build();
        }

        public async Task<string> GetToken()
        {
            AuthenticationResult result = await _confidentialClientApplication.AcquireTokenForClient(new string[] { _azureADOptions.Scope })
                   .ExecuteAsync();

            return result.AccessToken;
        }

        private X509Certificate2 GetCertificate()
        {
            var certificateClient = new CertificateClient(vaultUri: new Uri(_keyVaultOptions.URL), credential: new DefaultAzureCredential());
            var keyVaultCertificate = certificateClient.DownloadCertificate(_azureADOptions.KeyVaultCertificateName);
        
            return keyVaultCertificate;
        }
    }

Notice from the above snippets that you can set the certificate when configuring the IConfidentialApplication by calling the WithCertificate() method on ConfidentialClientApplicationBuilder and passing in the certificate of type X509Certificates.

Below snippets show the configurations in my app settings.

   "AzureADOptions": {
        "TenantId": "********-****-****-****-4a418e0cca7e",
        "ClientId": "********-****-****-****-4415a019b235",
        "Scope": "api://7********-****-****-****-9acf38c274a9/.default",
        "Authority": "https://login.microsoftonline.us/********-****-****-****-4a418e0cca7e/v2.0/",
        "KeyVaultCertificateName": "MyAppCert"
    },

    "KeyVault": {
        "URL": "https://keyvaulturl"
    },

Note that the azure credentials you use to access the key vault needs to have the Get and List certificate permissions. In my case, since I use managed identity, I assign the permissions to the managed identity.

Configure key vault access policy for retrieving certificates

References

How does a public key verify a signature

Quick Start: Set and retrieve a certificate from Azure Key Vault using the Azure portal

First case: Access token request with a shared secret

Second case: Access token request with a certificate

Microsoft identity platform application authentication certificate credentials

Using certificates with Microsoft.Identity.Web

Azure Key Vault Certificate client library for .NET

Microsoft Authentication Library for .NET (MSAL.NET)

No comments yet