Skip to content

Latest commit

 

History

History
221 lines (162 loc) · 11.6 KB

certificate-credentials.md

File metadata and controls

221 lines (162 loc) · 11.6 KB

Using certificate credentials with MSAL Node

⚠️ Before you start here, make sure you understand Initialize confidential client applications.

You can build confidential client applications with MSAL Node (web apps, daemon apps etc). A client credential is mandatory for confidential clients. Client credential can be:

  • managed identity: this is a certificatless scenario, where trust is established via the Azure infrastructure. No secret / certificate management is required. MSAL does not yet implement this feature, but you may use Azure Identity SDK instead. See https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/
  • clientSecret: a secret string generated during the app registration, or updated post registration for an existing application. This is not recommended for production.
  • clientCertificate: a certificate set during the app registration. The certificate needs to have the private key, because it will be used for signing an assertion that MSAL generates. The thumbprint is a X.509 SHA-1 thumbprint of the certificate (x5t), and the privateKey is the PEM encoded private key.
  • clientAssertion: instead of letting MSAL create an assertion, the app developer takes control. Useful for adding extra claims to the assertion or for using KeyVault for signing, instead of a local certificate. The certificate used to sign the assertion still needs to be set during app registration.

Note: 1p apps may be required to also send x5c. This is the X.509 certificate chain used in subject name/issuer auth scenarios.

Using secrets and certificates securely

Secrets should never be hardcoded. The dotenv npm package can be used to store secrets or certificates in a .env file (located in project's root directory) that should be included in .gitignore to prevent accidental uploads of the secrets.

Certificates can also be read-in from files via NodeJS's fs module. However, they should never be stored in the project's directory. Production apps should fetch certificates from Azure KeyVault, or other secure key vaults.

Please see certificates and secrets for more information.

See the MSAL sample: auth-code-with-certs

Registering certificates

If you do not have a certificate, you can create a self-signed certificate using PowerShell or using Azure KeyVault.

You need to upload your certificate to Azure AD.

  1. Navigate to Azure portal and select your Azure AD app registration.
  2. Select Certificates & secrets blade on the left.
  3. Click on Upload certificate and select the certificate file to upload (e.g. example.crt).
  4. Click Add. Once the certificate is uploaded, the thumbprint, start date, and expiration values are displayed.

For more information, see: Register your certificate with Microsoft identity platform

Initializing MSAL Node with certificates

const msal = require("@azure/msal-node");
require("dotenv").config(); // process.env now has the values defined in a .env file

const config = {
    auth: {
        clientId: "YOUR_CLIENT_ID",
        authority: "https://login.microsoftonline.com/YOUR_TENANT_ID",
        clientCertificate: {
            thumbprint: process.env.thumbprint, // a 40-digit hexadecimal string
            privateKey: process.env.privateKey,
        },
    },
};

// Create msal application object
const cca = new msal.ConfidentialClientApplication(config);

Both thumbprint and privateKey are expected to be strings. privateKey is further expected to be in the following form (PKCS#8):

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDkpKPrsfpIijS3
z2HCpDsa7dxOsKIrm7F1AtGBjyB0yVDjlh/FA7jT5sd2ypBh3FVsZGJudQsLRKfE
// ...
-----END ENCRYPTED PRIVATE KEY-----

ℹ️ Alternatively, your private key may begin with -----BEGIN PRIVATE KEY----- (unencrypted PKCS#8) or -----BEGIN RSA PRIVATE KEY----- (PKCS#1). These formats are also permissible. The following can be used to convert any compatible key to the PKCS#8 key type:

openssl pkcs8 -topk8 -inform PEM -outform PEM -in example.key -out example.key

If you have encrypted your private key (or if your private key is already encrypted) with a pass phrase, you'll need to decrypt it before passing it to MSAL Node.

Important: Never hardcode passwords in source code. Both the certificate private key and the optional descryption password should be fetched from a secure location (e.g. Azure KeyVault) and deployed securely with your web api.

This can be done using Node's crypto module. Use the createPrivateKey() method to parse and export your key:

const fs = require("fs");
const crypto = require("crypto");

const privateKeySource = fs.readFileSync("<path_to_key>/example.key");

const privateKeyObject = crypto.createPrivateKey({
    key: privateKeySource,
    passphrase: process.env.YOUR_PASSPHRASE,
    format: "pem",
});

const privateKey = privateKeyObject.export({
    format: "pem",
    type: "pkcs8",
});

(Optional) Converting pfx to pem

OpenSSL can be used for converting pfx encoded certificate files to pem:

    openssl pkcs12 -in certificate.pfx -out certificate.pem

If the conversion needs to happen programmatically, then you may have to rely on a 3rd party package, as Node.js offers no native method for this. For instance, using a popular TLS implementation like node-forge, you can do:

const forge = require("node-forge");

/**
 * @param {string} pfx: certificate + private key combination in pfx format
 * @param {string} passphrase: passphrase used to encrypt pfx file
 * @returns {Object}
 */
function convertPFX(pfx, passphrase = null) {
    const asn = forge.asn1.fromDer(forge.util.decode64(pfx));
    const p12 = forge.pkcs12.pkcs12FromAsn1(asn, true, passphrase);

    // Retrieve key data
    const keyData = p12
        .getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag })
        [forge.pki.oids.pkcs8ShroudedKeyBag].concat(
            p12.getBags({ bagType: forge.pki.oids.keyBag })[
                forge.pki.oids.keyBag
            ]
        );

    // Retrieve certificate data
    const certBags = p12.getBags({ bagType: forge.pki.oids.certBag })[
        forge.pki.oids.certBag
    ];
    const certificate = forge.pki.certificateToPem(certBags[0].cert);

    // Convert a Forge private key to an ASN.1 RSAPrivateKey
    const rsaPrivateKey = forge.pki.privateKeyToAsn1(keyData[0].key);

    // Wrap an RSAPrivateKey ASN.1 object in a PKCS#8 ASN.1 PrivateKeyInfo
    const privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey);

    // Convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM
    const privateKey = forge.pki.privateKeyInfoToPem(privateKeyInfo);

    console.log("Converted certificate: \n", certificate);
    console.log("Converted key: \n", privateKey);

    return {
        certificate: certificate,
        key: privateKey,
    };
}

(Optional) Creating an HTTPS server

The OAuth 2.0 protocol recommends using an HTTPS connection whenever possible. Most cloud services like Azure App Service will provide HTTPS connection by default via proxy. If for testing purposes you would like to setup your own HTTPS server, see the Node.js HTTPS guide.

You'll also need to add your self-signed certificates to the credential manager / key chain of your OS to bypass the browser's security policy. You may still see a warning in your browser afterwards (e.g. Chrome).

⚠️ You might need administrator privileges for running the commands above.

Common issues

In some cases, you may receive an error from Azure AD when trying to authenticate using certificates, such as the AADSTS700027: Client assertion contains an invalid signature error, indicating that the certificates and/or private keys that you use to initialize MSAL Node are malformed. A common reason for this is that the certificate / private key string that you are supplying to MSAL Node contains unexpected characters, such as return carriages (\r) or newlines (\n):

-----BEGIN CERTIFICATE-----\nMIIDDzCCAfegAwIBAgIJAMkyzQVK88NHMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYDVQQGEwJTRTESMBAGA1UECBMJU3RvY2tob2xtMQ4wDAYDVQQHEwVLaXN0YTEQMA4G0fbkqbKulrchGbNgkankZtEVg4PGjobZq7B+njvcVa7SsWF/WLq5AUbw==\r\n-----END CERTIFICATE-----

Alternatively, your certificate / key file may contain bag attributes:

Bag Attributes
    localKeyID: 28 B5 8E 16 11 88 E9 00 58 D5 76 30 12 B9 59 B8 E4 CE 7C AA
subject=/C=UK/ST=Suffolk/L=Ipswich/O=Example plc/CN=alice
issuer=/C=UK/ST=Suffolk/L=Ipswich/O=Example plc/CN=Certificate Authority/emailAddress=ca@example.com\n
-----BEGIN CERTIFICATE-----
MIIDDzCCAfegAwIBAgIJAMkyzQVK88NHMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD
VQQGEwJTRTESMBAGA1UECBMJU3RvY2tob2xtMQ4wDAYDVQQHEwVLaXN0YTEQMA4G
0fbkqbKulrchGbNgkankZtEVg4PGjo+Y8MdMjtfSZB29hwYvfMX09jzJ68ZqmpYQ
njvcVtLbEZN5OGCkaslb/f2OxLbsUNgIbws538WnaaufDvKmQe2kUdWmpl9Wn9Bf
bZq7B+njvcVa7SsWF/WLq5AUbw==
-----END CERTIFICATE-----

In such cases, you are responsible for cleaning the string before you pass them to MSAL Node configuration. For instance:

const msal = require("@azure/msal-node");
const fs = require("fs");

const privateKeySource = fs.readFileSync("<path_to_key>/certs/example.key");
const privateKey = Buffer.from(privateKeySource, "base64")
    .toString()
    .replace(/\r/g, "")
    .replace(/\n/g, "");

const config = {
    auth: {
        clientId: "YOUR_CLIENT_ID",
        authority: "https://login.microsoftonline.com/YOUR_TENANT_ID",
        clientCertificate: {
            thumbprint: process.env.thumbprint, // a 40-digit hexadecimal string
            privateKey: privateKey,
        },
    },
};

// Create msal application object
const cca = new msal.ConfidentialClientApplication(config);

More Information