DefaultAzureCredential with certificate-based authentication: how to make it work
Kinga
Posted on August 18, 2023
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 theAZURE_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 withClientSecretCredential
-
AZURE_CLIENT_CERTIFICATE_PATH
- The path to a PEM-formatted certificate file in the deployment environment to be used with theClientCertificateCredential
-
AZURE_USERNAME
andAZURE_PASSWORD
- The username and password pair to be used with theUsernamePasswordCredential
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",
}
}
};
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
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-----
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/"
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/"
}
}
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) + "...")
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.
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
Reason: The referenced file contains certificate only.
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
August 18, 2023