node.jsazureazure-ad-msalazure-authenticationazure-service-principal

How to implement OAuth 2.0 certificate authentication in Node JS


I'm working on a Node.js application that uses Microsoft Authentication Library (MSAL) to authenticate with Microsoft Graph API using OAuth 2.0.

Currently, authentication is handled using a client secret, but the goal is to improve security by switching to a client certificate instead.

Here’s the current implementation for acquiring an access token with the clientSecret:

const config = {
    auth: {
        clientId: clientId,
        authority: `https://login.microsoftonline.com/${tenantId}`,
        clientSecret: clientSecret,
    }
};

const cca = new msal.ConfidentialClientApplication(config);

const request = {
    scopes: ['https://graph.microsoft.com/.default'],
};

async function getAccessToken() {
    try {
        const response = await cca.acquireTokenByClientCredential(request);
        return response.accessToken;
    } catch (error) {
        console.error(`Error acquiring token: ${error}`);
    }
}

Additionally, this function is used to fetch a list of users from Microsoft Graph API:

async function listUsers() {
    const token = await getAccessToken();
    if (!token) {
        console.error('Failed to acquire access token');
        return;
    }

    const options = {
        headers: {
            'Authorization': `Bearer ${token}`
        }
    };

    try {
        const response = await axios.get('https://graph.microsoft.com/v1.0/users', options);
        console.log(response.data);
    } catch (error) {
        console.error(`Error fetching users: ${error}`);
    }
}

The goal now is to replace the clientSecret with a client certificate to enhance security. What changes are needed to modify the MSAL configuration for using client certificate instead?


Solution

  • In my case, I used below commands to create private key and certificate:

    openssl genrsa -out private.key 2048
    openssl req -new -key private.key -out request.csr
    openssl x509 -req -in request.csr -signkey private.key -out certificate.crt -days 365
    Get-Content private.key, certificate.crt | Set-Content certificate.pem
    

    Now, make sure to upload certificate.crt file in your Entra ID app registration like this:

    enter image description here

    When I ran below modified code, I got the list of users successfully in my Node JS application:

    const msal = require('@azure/msal-node');
    const fs = require('fs');
    
    const clientId = 'appId';
    const tenantId = 'tenantId';
    
    const certificatePath = './certificate.pem';
    const certificate = fs.readFileSync(certificatePath, 'utf8');
    
    const config = {
        auth: {
            clientId: clientId,
            authority: `https://login.microsoftonline.com/${tenantId}`,
            clientCertificate: {
                thumbprint: '5AA5151C05CC022xxxxxxxxxxxxx', 
                privateKey: certificate,
            },
        },
    };
    
    const cca = new msal.ConfidentialClientApplication(config);
    
    const request = {
        scopes: ['https://graph.microsoft.com/.default'],
    };
    
    async function getAccessToken() {
        try {
            const response = await cca.acquireTokenByClientCredential(request);
            return response.accessToken;
        } catch (error) {
            console.error(`Error acquiring token: ${error}`);
        }
    }
    
    async function listUsers() {
        const token = await getAccessToken();
        if (!token) {
            console.error('Failed to acquire access token');
            return;
        }
    
        const axios = require('axios');
        const options = {
            headers: {
                'Authorization': `Bearer ${token}`,
            },
        };
    
        try {
            const response = await axios.get('https://graph.microsoft.com/v1.0/users', options);
            console.log(response.data);
        } catch (error) {
            console.error(`Error fetching users: ${error}`);
        }
    }
    
    listUsers();
    

    Response:

    enter image description here