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.
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
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.
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.
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.
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
How to retrieve connection strings in azure key vault from ASP.NET using configuration builders, XML transformation and azure devops.
Securely log to blob storage using NLog with connection string in key vault.
Connect to azure key vault from an ASP.NET core app using azure managed identity
Access azure key vault from an ASP.NET core app on IIS using X.509 certificate
Three ways of authenticating a Windows virtual machine against Azure Key Vault.
Secure app settings in ASP.NET Core 2
Building a fully multitenant system using Microsoft Identity Framework and SQL Row Level Security
Analyzing a rental property through automation