Search code examples
node.jsazure-functionsmicrosoft-graph-apiazure-keyvaultazure-container-registry

How to use a certificate for MS Graph authentication from containerized Node.js Azure Function?


We've got a containerized Azure Function, implemented as Node.js application. The container is pulled from Azure Container Registry.

This Node.js app - which runs in the container - needs to access Microsoft Graph (as application, without user context). Basically like shown here:

https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-nodejs-console

But with the following differences:

  • we want to use a certificate to authenticate (no client secret)
  • the certificate comes from Azure Key Vault, either directly or via the Function App's TLS settings (that is also able to pull from Key Vault)
  • and we need to do it from inside the container

(Why Azure Key Vault? Because we want to use its features like automated renewal.)

How would we implement this? The container part is making things complicated it seems.


Solution

  • You could get the certificate from keyvault.

    const { DefaultAzureCredential } = require("@azure/identity");
    const { CertificateClient } = require("@azure/keyvault-certificates");
    
    const credential = new DefaultAzureCredential();
    
    const vaultName = "<YOUR KEYVAULT NAME>";
    const url = `https://${vaultName}.vault.azure.net`;
    
    const client = new CertificateClient(url, credential);
    
    const certificateName = "MyCertificateName";
    
    async function main() {
      const latestCertificate = await client.getCertificate(certificateName);
      console.log(`Latest version of the certificate ${certificateName}: `, latestCertificate);
      const specificCertificate = await client.getCertificateVersion(
        certificateName,
        latestCertificate.properties.version
      );
      console.log(
        `The certificate ${certificateName} at the version ${latestCertificate.properties.version}: `,
        specificCertificate
      );
    }
    
    main();
    

    Then get the access token for Microsoft Graph with the adal, don't forget to change the resource to https://graph.microsoft.com, also change the values to yours.

    'use strict';
    
    var fs = require('fs');
    var adal = require('adal-node');
    
    
    var AuthenticationContext = adal.AuthenticationContext;
    
    function turnOnLogging() {
      var log = adal.Logging;
      log.setLoggingOptions(
      {
        level : log.LOGGING_LEVEL.VERBOSE,
        log : function(level, message, error) {
          console.log(message);
          if (error) {
            console.log(error);
          }
        }
      });
    }
    
    function getPrivateKey(filename) {
      var privatePem = fs.readFileSync(filename, { encoding : 'utf8'});
      return privatePem;
    }
    
    /*
     * You can override the default account information by providing a JSON file
     * with the same parameters as the sampleParameters variable below.  Either
     * through a command line argument, 'node sample.js parameters.json', or
     * specifying in an environment variable.
     * privateKeyFile must contain a PEM encoded cert with private key.
     * thumbprint must be the thumbprint of the privateKeyFile.
     * {
     *   tenant : 'naturalcauses.onmicrosoft.com',
     *   authorityHostUrl : 'https://login.windows.net',
     *   clientId : 'd6835713-b745-48d1-bb62-7a8248477d35',
     *   thumbprint : 'C1:5D:EA:86:56:AD:DF:67:BE:80:31:D8:5E:BD:DC:5A:D6:C4:36:E1',
     *   privateKeyFile : 'ncwebCTKey.pem'
     * }
     */
    var parametersFile = process.argv[2] || process.env['ADAL_SAMPLE_PARAMETERS_FILE'];
    
    var sampleParameters;
    if (parametersFile) {
      var jsonFile = fs.readFileSync(parametersFile);
      if (jsonFile) {
        sampleParameters = JSON.parse(jsonFile);
      } else {
        console.log('File not found, falling back to defaults: ' + parametersFile);
      }
    }
    
    sampleParameters = {
      tenant : 'naturalcauses.com',
      authorityHostUrl : 'https://login.windows.net',
      clientId : 'd6835713-b745-48d1-bb62-7a8248477d35',
      thumbprint : 'C15DEA8656ADDF67BE8031D85EBDDC5AD6C436E1',
      privateKeyFile : ''
    };
    
    var authorityUrl = sampleParameters.authorityHostUrl + '/' + sampleParameters.tenant;
    
    var resource = 'https://graph.microsoft.com';
    
    turnOnLogging();
    
    var context = new AuthenticationContext(authorityUrl);
    var key = getPrivateKey(sampleParameters.privateKeyFile);
    
    context.acquireTokenWithClientCertificate(resource, sampleParameters.clientId, key, sampleParameters.thumbprint, function(err, tokenResponse) {
      if (err) {
        console.log('well that didn\'t work: ' + err.stack);
      } else {
        console.log(tokenResponse);
      }
    });