javascriptnode.jsazureazure-identity

Use pfx certificate to get Microsoft Entra token - Error: secretOrPrivateKey must be an asymmetric key when using RS256


I tried different ways to get an Azure AD token using a pfx certificate. But the code below is persistently returning this error: Error: secretOrPrivateKey must be an asymmetric key when using RS256

I also tried using @azure/keyvault-certificates and azure/identity, then genrating .pem files with open ssl, and in that case I also get the same error.

const axios = require('axios');
const fs = require('fs');
const path = require('path');
const https = require('https');
const qs = require('querystring');
const jwt = require('jsonwebtoken'); 

const certPath = path.join(__dirname, 'certificate.pfx');
const certBuffer = fs.readFileSync(certPath);

const tokenEndpoint = 'https://login.microsoftonline.com/my-tenant-id/oauth2/token';
const clientId = 'my-app-id';

const tokenPayload = {
    aud: tokenEndpoint,
    iss: clientId,
    sub: clientId,
    jti: Math.random().toString(36).substring(7),
    nbf: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + 3600,
};

const tokenOptions = {
    algorithm: 'RS256',
};

const clientAssertion = jwt.sign(tokenPayload, certBuffer, tokenOptions);
console.log('Client Assertion:', clientAssertion);

const postData = {
    grant_type: 'client_credentials',
    client_id: clientId,
    resource: 'resource',
    client_assertion: clientAssertion,
    client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
};

const axiosConfig = {
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
    },
    httpsAgent: new https.Agent({
        pfx: certBuffer,
        passphrase: '',
        rejectUnauthorized: true,
    }),
};

async function getToken() {
    try {
        const response = await axios.post(tokenEndpoint, qs.stringify(postData), axiosConfig);
        const token = response.data.access_token;
        console.log('Token:', token);
        return token;
    } catch (error) {
        console.error('Error:', error.message);
        if (error.response) {
            console.error('Error response from Azure AD:', error.response.data);
        }
        throw error;
    }
}

getToken().then(token => {
    console.log('Token:', token);
}).catch(err => {
    console.error('Failed to obtain token:', err);
});

Solution

  • In my case, I ran below OpenSSL commands to create private key and certificate like this:

    openssl genrsa -out sridemo.pem 2048
    openssl req -new -key sridemo.pem -out sridemo.csr
    openssl x509 -req -days 365 -in sridemo.csr -signkey sridemo.pem -out sridemo.crt
    

    Response:

    enter image description here

    Make sure to upload sridemo.crt file to your Entra ID app registration and note thumbprint value:

    enter image description here

    To generate Microsoft Entra token using certificate, I used @azure/msal-node with below sample code:

    const fs = require('fs');
    const msal = require('@azure/msal-node');
    
    const tenantId = 'tenant_id';
    const clientId = 'appId';
    const authority = `https://login.microsoftonline.com/${tenantId}`;
    const certPath = 'path/to/your/certificate.pem';
    const certThumbprint = 'cert_thumbprint';
    const scope = 'https://graph.microsoft.com/.default';
    
    // Read the certificate
    const privateKey = fs.readFileSync(certPath, 'utf8');
    
    const confidentialClientConfig = {
        auth: {
            clientId: clientId,
            authority: authority,
            clientCertificate: {
                thumbprint: certThumbprint,
                privateKey: privateKey,
            },
        },
    };
    
    const cca = new msal.ConfidentialClientApplication(confidentialClientConfig);
    
    // Acquire a token
    async function getToken() {
        try {
            const tokenResponse = await cca.acquireTokenByClientCredential({
                scopes: [scope],
            });
            console.log('Access Token:', tokenResponse.accessToken);
        } catch (error) {
            console.error('Error acquiring token:', error);
        }
    }
    
    getToken();
    

    Response:

    enter image description here

    To confirm that, you can decode the token in jwt.ms and check claims as below:

    enter image description here

    Reference:

    MSAL NODE · AzureAD/microsoft-authentication-library-for-js (github.com)