Certificates - mattchenderson/microsoft-identity-web GitHub Wiki

Using certificates with Microsoft.Identity.Web

Microsoft.Identity.Web uses certificates in two situations:

  • In web apps and web APIs, to prove the identity of the application, instead of using a client secret.
  • In web APIs, to decrypt tokens if the web API opted to get encrypted tokens.

Also certificates can be specified both by configuration, in the configuration file, or programmatically.

This article explains both usages, and describes the certificates to use.

Table of contents:

Client certificates

Web apps and web APIs are confidential client applications.

They can prove their identity to Azure AD or Azure AD B2C by three means:

Method Supported in Microsoft.Identity.Web
Client secrets Yes
Client certificates Yes
Client assertions Not yet

Microsoft.Identity.Web supports specifying client certificates. The configuration property to specify the client certificates is ClientCertificates. It's an array of certificate descriptions. There are several ways of describing certificates. see Specifying certificates below.

Describing client certificates to use by configuration

You can express the client certificates in the ClientCertificates property. ClientCertificates and ClientSecret are mutually exclusive.

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "msidentitysamplestesting.onmicrosoft.com",
    "TenantId": "7f58f645-c190-4ce5-9de4-e2b7acd2a6ab",
    "ClientId": "86699d80-dd21-476a-bcd1-7c1a3d471f75",
    "ClientCertificates": [
      {
        "SourceType": "KeyVault",
        "KeyVaultUrl": "https://msidentitywebsamples.vault.azure.net",
        "KeyVaultCertificateName": "MicrosoftIdentitySamplesCert"
      }
     ]
  }
}

See Specifying certificates below for all the ways to describe certificates.

Describing client certificates to use programmatically

You can also specify the certificate description programmatically. For this, you add CertificateDescription instances to the ClientCertificates property of MicrosoftIdentityOptions. You can then use some of the overloads of AddMicrosoftIdentityWebApp, using delegates to set the MicrosoftIdentityOptions.

For a Web app, this would look like the following:

using Microsoft.Identity.Web;
public class Startup
{
 // More code here
 public void ConfigureServices(IServiceCollection services)
 {
  // More code here
  services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(microsoftIdentityOptions=>
    {
      Configuration.Bind("AzureAd", microsoftIdentityOptions);
      microsoftIdentityOptions.ClientCertificates = new CertificateDescription[] {
        CertificateDescription.FromKeyVault("https://msidentitywebsamples.vault.azure.net",
                                            "MicrosoftIdentitySamplesCert")};
    })
  .EnableTokenAcquisitionToCallDownstreamApi(confidentialClientApplicationOptions=>
    {
    Configuration.Bind("AzureAd", confidentialClientApplicationOptions); 
    })
  .AddInMemoryTokenCaches();
 }
}

For a web API, the code snippet, becomes:

using Microsoft.Identity.Web;
public class Startup
{
 // More code here
 public void ConfigureServices(IServiceCollection services)
 {
  // More code here
  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
   .AddMicrosoftIdentityWebApi(
     configureJwtBearerOptions =>
     {
      Configuration.Bind("AzureAd", configureJwtBearerOptions);
     }, microsoftIdentityOptions=>
     {
      Configuration.Bind("AzureAd", microsoftIdentityOptions);
      microsoftIdentityOptions.TokenDecryptionCertificates = new CertificateDescription[] {
         CertificateDescription.FromKeyVault("https://msidentitywebsamples.vault.azure.net",
                                             "MicrosoftIdentitySamplesDecryptCert")};
     })
   .EnableTokenAcquisitionToCallDownstreamApi(
     confidentialClientApplicationOptions=>
     {
      Configuration.Bind("AzureAd", confidentialClientApplicationOptions); 
     })
   .AddInMemoryTokenCaches();
 }
}

See Specifying certificates below for all the ways to specify client certificates.

Helping certificate rotation by sending x5c

If a certificate expires, Microsoft.Identity.Web will attempt to reload it once, helping certificate rotation in the case where the certificate description references KeyVault and a new version of the certificate is available.

It's also possible to specify if the x5c claim (public key of the certificate) should be sent to Azure AD each time the web app or web API calls Azure AD. Sending the x5c enables application developers to achieve easy certificate rollover in Azure AD: this method will send the public certificate to Azure AD along with the token request, so that Azure AD can use it to validate the subject name based on a trusted issuer policy. This saves the application admin from the need to explicitly manage the certificate rollover (either via portal or PowerShell/CLI operation). For details see https://aka.ms/msal-net-sni.

To specify to send the x5c claim, set the boolean SendX5C property of the options to true either by configuration or programmatically.

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "msidentitysamplestesting.onmicrosoft.com",
    "TenantId": "7f58f645-c190-4ce5-9de4-e2b7acd2a6ab",
    "ClientId": "86699d80-dd21-476a-bcd1-7c1a3d471f75",
    "TokenDecryptionCertificates": [
      {
        "SourceType": "KeyVault",
        "KeyVaultUrl": "https://msidentitywebsamples.vault.azure.net",
        "KeyVaultCertificateName": "MicrosoftIdentitySamplesCert"
      }
     ],
     "SendX5C": "true"
  }
}

Decryption certificates

Web APIs can request token encryption (for privacy reasons). This is even compulsory for first-party (Microsoft) web APIs that access MSA identities. The configuration property to specify the client certificates is TokenDecryptionCertificates. It's an array of descriptions of certificates.

Describing decryption certificates to use by configuration

You can express the decryption certificates in the TokenDecryptionCertificates property.

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "msidentitysamplestesting.onmicrosoft.com",
    "TenantId": "7f58f645-c190-4ce5-9de4-e2b7acd2a6ab",
    "ClientId": "86699d80-dd21-476a-bcd1-7c1a3d471f75",
    "TokenDecryptionCertificates": [
      {
        "SourceType": "KeyVault",
        "KeyVaultUrl": "https://msidentitywebsamples.vault.azure.net",
        "KeyVaultCertificateName": "MicrosoftIdentitySamplesCert"
      }
     ]
  }
}

See Specifying certificates below for all the ways to describe certificates.

Describing decryption certificates to use programmatically

You can also specify the certificate description programmatically using the overload of AddMicrosoftIdentityWebApi that take delegate parameters, by setting the TokenDecryptionCertificates property of the MicrosoftIdentityOptions parameter of the delegate.

using Microsoft.Identity.Web;
public class Startup
{
 // More code here
 public void ConfigureServices(IServiceCollection services)
 {
  // More code here
  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
   .AddMicrosoftIdentityWebApi(
     configureJwtBearerOptions => {
      Configuration.Bind("AzureAd", configureJwtBearerOptions);
     },
     microsoftIdentityOptions=> {
      Configuration.Bind("AzureAd", microsoftIdentityOptions);
      microsoftIdentityOptions.TokenDecryptionCertificates = new CertificateDescription[] {
         CertificateDescription.FromKeyVault("https://msidentitywebsamples. vault.azure.net",
                                             "MicrosoftIdentitySamplesCert")};
     })
   .EnableTokenAcquisitionToCallDownstreamApi(
     confidentialClientApplicationOptions=> {
      Configuration.Bind("AzureAd", confidentialClientApplicationOptions); 
     })
   .AddInMemoryTokenCaches();
 }

The code snippets below only describe the lines used to get a certificate description to fill-in the collection of certificate descriptions (therefore replacing the following lines from the code snippet above:

        CertificateDescription.FromKeyVault("https://msidentitywebsamples. vault.azure.net",
                                            "MicrosoftIdentitySamplesCert")};

See Specifying certificates below for all the ways to describe certificates.

Controlling where to get the private key from.

By default, for the methods that require it, Microsoft.Identity.Web gets the private from the machine key set and doesn't write it on disk (it uses the following X509KeyStorageFlags: X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet. From Microsoft.Identity.Web 1.7.0, it's possible to specify the X509KeyStorageFlags in the certificate description (both in the config file, or programmatically). if you want to use other storage flags than the default ones.

Ways of specifying certificates

You can describe the certificates to load, either by configuration, or programmatically:

  • from the certificate store (Windows) and a thumbprint ("440A5BE6C4BE2FF02A0ADBED1AAA43D6CF12E269"),
  • from the certificate store (Windows) and a distinguished name ("CN=TestCert"),
  • from a path on the disk and optionally a password (probably only for debugging locally),
  • directly from a Base64 representation of the certificate,
  • from Azure Key Vault,
  • directly providing it (programmatically only). Describing the certificate by configuration allows for just-in-time loading, rather than paying the startup cost. For instance for a web app that signs in a user, don(t load the certificate until an access token is needed to call a web API. When your certificate is in Key Vault, Microsoft.Identity.Web uses Managed Identity, therefore enabling your application to have the same code when deployed (for instance on a VM or Azure app services), or locally on your developer box (using developer credentials).

Specifying certificates as an X509Certificate2

You can also directly specify the certificate description as an X509Certificate2 that would you've loaded. This is only possible programmatically, both for client certificates:

microsoftIdentityOptions.ClientCertificates = new CertificateDescription[] {
 TokenDecryptionCertificates.FromCertificate(x509certificate2)
};

and for token decryption certificates:

microsoftIdentityOptions.TokenDecryptionCertificates = new CertificateDescription[] {
 CertificateDescription.FromCertificate(x509certificate2)
};

Getting certificates from Key Vault

To fetch certificates from KeyVault, Microsoft.Identity.Web uses Managed Identity through the Azure SDK DefaultAzureCredential. This works seamlessly on you developer machine using your developer credentials (used in Visual Studio, Azure CLI, Azure PowerShell), and also when deployed with Service fabric or App Services in Azure provided you've been using a System-assigned Managed identity. However:

  • If you're using a User-assigned managed identity, you'll need to set the UserAssignedManagedIdentityClientId configuration property or set an environment variable AZURE_CLIENT_ID to be the user-assigned managed identity clientID. You can do that through the Azure portal:
    1. Go to Azure App Service -> Settings | Configuration -> Application Settings
    2. Add or update the AZURE_CLIENT_ID app setting to the user assigned managed identity ID.
  • When, on your developer machine, you have several accounts in Visual Studio, you'll need to specify which account to use, by setting another environment variable AZURE_USERNAME

Specifying certificates

The following table shows all the ways to specify client certificates by configuration or programmatically. To specify token decryption certificates instead of, or in addition to, client certificates, just replace ClientCertificates by TokenDecryptionCertificates.

How to get the certificate By configuration Programmatically
From KeyVault
{
 "ClientCertificates": [
  {
  "SourceType": "KeyVault",
  "KeyVaultUrl": "https://msidentitywebsamples.vault.azure.net",
  "KeyVaultCertificateName": "MicrosoftIdentitySamplesCert"
  }
 ]
}
microsoftIdentityOptions.ClientCertificates = new CertificateDescription[] {
 CertificateDescription.FromKeyVault("https://msidentitywebsamples.vault.azure.net",
                                     "MicrosoftIdentitySamplesCert")
};
From a path
{
 "ClientCertificates": [
 {
  "SourceType": "Path",
  "CertificateDiskPath": "c:\\temp\\WebAppCallingWebApiCert.pfx",
  "CertificatePassword": "password"
 }]
}
microsoftIdentityOptions.ClientCertificates = new CertificateDescription[] {
 CertificateDescription.FromPath(@"c:\temp\WebAppCallingWebApiCert.pfx",
                                     "password")
};
By distinguished name
{
 "ClientCertificates": [
 {
  "SourceType": "StoreWithDistinguishedName",
  "CertificateStorePath": "CurrentUser/My",
  "CertificateDistinguishedName": "CN=WebAppCallingWebApiCert"
 }]
}
microsoftIdentityOptions.ClientCertificates = new CertificateDescription[] {
 CertificateDescription.FromStoreWithDistinguishedName(StoreLocation.CurrentUser,
                                     StoreName.My,
                                     "CN=WebAppCallingWebApiCert")
};
By thumbprint
{
 "ClientCertificates": [
 {
  "SourceType": "StoreWithThumbprint",
  "CertificateStorePath": "CurrentUser/My",
  "CertificateThumbprint": "962D129A...D18EFEB6961684"
 }]
}
microsoftIdentityOptions.ClientCertificates = new CertificateDescription[] {
 CertificateDescription.FromStoreWithThumbprint(StoreLocation.CurrentUser,
                                     StoreName.My,
                                     "962D129A...D18EFEB6961684")
};
By Base64 encoding
{
 "ClientCertificates": [
 {
  "SourceType": "Base64Encoded",
  "Base64EncodedValue": "MIIDHzCgegA.....r1n8Ta0="
 }]
}
microsoftIdentityOptions.ClientCertificates = new CertificateDescription[] {
 CertificateDescription.FromBase64Encoded("MIIDHzCgegA.....r1n8Ta0=")
};

Microsoft Identity Web classes used for certificate management

This is a class diagram showing how the classes involved in certificate management in Microsoft.Identity.Web are articulated:

image

⚠️ **GitHub.com Fallback** ⚠️