DefaultAzureCredential with certificate-based authentication: how to make it work

kkazala

Kinga

Posted on August 18, 2023

DefaultAzureCredential with certificate-based authentication: how to make it work

TL;DR;

When using DefaultAzureCredential, generate PEM certificate and private key files.

  • The cert.pem needs to be uploaded to your AAD application registration.
  • Then, create a new BOTH.pem file containing both, the certificate and the private key. This is the file you need to reference in the AZURE_CLIENT_CERTIFICATE_PATH

The authentication flow

DefaultAzureCredential class

DefaultAzureCredential is a default credential capable of automatically handling most Azure SDK authentication scenarios.

The identity it uses depends on the environment. When an access token is needed, it requests one using these identities in turn, stopping when one provides a token:

  • Environment - The DefaultAzureCredential will read account information specified via environment variables and use it to authenticate.
  • Workload Identity - If the application is deployed to an Azure host with Workload Identity enabled, the DefaultAzureCredential will authenticate with that account.
  • Managed Identity - If the application is deployed to an Azure host with Managed Identity enabled, the DefaultAzureCredential will authenticate with that account.
  • etc...

EnvironmentCredential class

By specifying environment variables, we instruct the DefaultAzureCredential to use EnvironmentCredential class.

The EnvironmentCredential, based on the environment variable names, determines how it should authenticate. It effectively acts as a wrapper for the ClientSecretCredential, ClientCertificateCredential or UsernamePasswordCredential.
In all cases, the AZURE_TENANT_ID and AZURE_CLIENT_ID environment variables are expected to be present to use this credential as they identify your application. The following environment variables will then be tried in order:

  • AZURE_CLIENT_SECRET - A client secret to be used with ClientSecretCredential
  • AZURE_CLIENT_CERTIFICATE_PATH - The path to a PEM-formatted certificate file in the deployment environment to be used with the ClientCertificateCredential
  • AZURE_USERNAME and AZURE_PASSWORD - The username and password pair to be used with the UsernamePasswordCredential

ClientCertificateCredential class

Using a certificate to authenticate is recommended as it is generally more secure than using a client secret.

To use this authentication method, you must generate your own PEM-formatted certificate and register it in the "Certificates & secrets" page for your app registration.

Next, you specify the following environment variables:

Variable name Value
AZURE_CLIENT_ID ID of an Azure AD application
AZURE_TENANT_ID ID of the application's Azure AD tenant
AZURE_CLIENT_CERTIFICATE_PATH path to a PFX or PEM-encoded certificate file including private key 🤔
AZURE_CLIENT_CERTIFICATE_PASSWORD (optional) the password protecting the certificate file (currently only supported for PFX (PKCS12) certificates)
AZURE_CLIENT_SEND_CERTIFICATE_CHAIN (optional) send certificate chain in x5c header to support subject name / issuer based authentication

MsalClientCertificate class

The ClientCertificateCredential creates an instance of the MsalClientCertificate class. According to the Using certificate credentials with MSAL Node, MSAL Node requires the following configuration:

const config = {
    auth: {
        clientId: "YOUR_CLIENT_ID",
        authority: "https://login.microsoftonline.com/YOUR_TENANT_ID",
        clientCertificate: {
            thumbprint: "CERT_THUMBPRINT", // a 40-digit hexadecimal string 
            privateKey: "CERT_PRIVATE_KEY",
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

The MsalClientCertificate class inherits from MsalNode and takes over the job of generating the certificate thumbprint and private key parameters.

It first executes parseCertificate() to generate the certificate thumbprint based on the PEM file referenced in AZURE_CLIENT_CERTIFICATE_PATH, ensuring that the file contains -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----.

Next, if the certificatePassword is present, it will decode the private key provided in the AZURE_CLIENT_CERTIFICATE_PATH file. Otherwise, it returns the private key from the AZURE_CLIENT_CERTIFICATE_PATH file.

Do you see what is happening? The MsalClientCertificate class expects your local PEM file to contain 👉 BOTH, the certificate file (the same you uploaded to Azure) and the private key. 👈

Wrapping up

Generate certificate

You may use the following command to generate PEM certificates:

openssl req `
    -x509 `
    -days 365 `
    -newkey rsa:2048 `
    -keyout keyencrypted.pem `
    -out cert.pem `
    -subj '/CN=AuthTestWithPassword/C=CH/ST=Zurich/L=Zurich' `
    -passout pass:HereIsMySuperPass
Enter fullscreen mode Exit fullscreen mode

Make sure the key size is at least 2048 bits, as required by jsonwebtoken version 9.

After creating Service Principal (see Application and service principal objects in Azure Active Directory), save the cert.pem to "Certificates & secrets"

Next, create a new PEM file, containing BOTH, the certificate and the private key. The file should have the following format:

BOTH.pem

-----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIUKoqrm/mWZ2EoC/a60vBoAXxoEMEwDQYJKoZIhvcNAQEL
...
tjs1jav+97FKR1lLnyGS90e2LjtTjLzqy1O5k8T1+6sv
-----END CERTIFICATE-----
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIZ6SyJiacCZwCAggA
...
UaTp6QTCR5NvDEb3iPNbQw==
-----END ENCRYPTED PRIVATE KEY-----
Enter fullscreen mode Exit fullscreen mode

Add Environment variables

You may set the environment variable using PowerShell ($env:Path = 'C:\foo;') but I prefer using an .env file. It's easier to swap configurations as needed. But remember to only import it when in development mode =)

In the root of your project create .env.cert file, and set the required values:

.env.cert

TenantName= "{tenant-name}"
AZURE_CLIENT_ID= "{client-id}"
AZURE_TENANT_ID= "{tenant-id}"
AZURE_CLIENT_CERTIFICATE_PATH= "./temp/BOTH.pem"
AZURE_CLIENT_CERTIFICATE_PASSWORD= "HereIsMySuperPass"

AZURE_CLOUD_INSTANCE= "https://login.microsoftonline.com/"
GRAPH_API_ENDPOINT= "https://graph.microsoft.com/"
Enter fullscreen mode Exit fullscreen mode

Or instead, if you are writing Azure Function, simply use local.settings.json

local.settings.json

{
  "IsEncrypted": false,
  "Values": {
    //...
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "FUNCTIONS_EXTENSION_VERSION": "~4",
    "WEBSITE_NODE_DEFAULT_VERSION": "~18",
    "TenantName": "{tenant-name}",
    "AZURE_CLIENT_ID": "{client-id}",
    "AZURE_TENANT_ID": "{tenant-id}",
    "AZURE_CLIENT_CERTIFICATE_PATH": "./temp/BOTH.pem",
    "AZURE_CLIENT_CERTIFICATE_PASSWORD": "HereIsMySuperPass",
    "AZURE_CLOUD_INSTANCE": "https://login.microsoftonline.com/",
    "GRAPH_API_ENDPOINT": "https://graph.microsoft.com/"
    }
}
Enter fullscreen mode Exit fullscreen mode

Your code

The setup is now ready and you can use the DefaultAzureCredential to authenticate. Make sure to import the environment variables from the .env.cert

import dotenv from "dotenv"
import { AccessToken, DefaultAzureCredential } from "@azure/identity";

if (process.env.NODE_ENV !== 'production') {
    dotenv.config({ path: "./.env.cert" })
}

const credential = new DefaultAzureCredential()

const resultGraph: AccessToken = await credential.getToken("https://graph.microsoft.com/.default")
const resultSPO: AccessToken = await credential.getToken(`https://${process.env.TenantName}.sharepoint.com/.default`)

console.log("Auth SPO: ", resultSPO.token.slice(0, 10) + "...")
console.log("Auth Graph: ", resultGraph.token.slice(0, 10) + "...")
Enter fullscreen mode Exit fullscreen mode

Errors

If you do not provide correct local PEM file, you may expect the following errors:

The file at the specified path does not contain a PEM-encoded certificate.

Exception: EnvironmentCredential authentication failed. To troubleshoot, visit https://aka.ms/azsdk/js/identity/environmentcredential/troubleshoot. Status code: 400
More details:
The file at the specified path does not contain a PEM-encoded certificate.
Enter fullscreen mode Exit fullscreen mode

Reason: The referenced file contains private key only.

Failed to read private key

Failed to read private key
Stack: AuthenticationError: EnvironmentCredential authentication failed. To troubleshoot, visit <https://aka.ms/azsdk/js/identity/environmentcredential/troubleshoot>. Status code: 400
Enter fullscreen mode Exit fullscreen mode

Reason: The referenced file contains certificate only.

💖 💪 🙅 🚩
kkazala
Kinga

Posted on August 18, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related