Skip to content

Instantly share code, notes, and snippets.

@phillipharding
Last active July 8, 2023 14:04
Show Gist options
  • Save phillipharding/229292754a56a9fcf6ea49bc9b4a3780 to your computer and use it in GitHub Desktop.
Save phillipharding/229292754a56a9fcf6ea49bc9b4a3780 to your computer and use it in GitHub Desktop.
Verifies an access token issued by the Azure AD identity platform
{
"name": "jwt-validate",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"jsonwebtoken": "^8.5.1",
"jwks-rsa": "^2.0.5",
"node-fetch": "^2.6.1",
"rsa-pem-from-mod-exp": "^0.8.4"
}
}
/** References:
* https://www.npmjs.com/package/azure-ad-verify-token
* https://github.com/justinlettau/azure-ad-verify-token
* https://liangjunjiang.medium.com/verify-and-decode-azure-activity-directory-token-bc72cf7010bc
* https://liangjunjiang.medium.com/azure-active-directory-api-v1-0-vs-v2-0-5c75fb2b1154
* https://www.npmjs.com/package/jwks-rsa
* https://www.npmjs.com/package/jsonwebtoken
* https://www.voitanos.io/blog/validating-azure-ad-generated-oauth-tokens
*
* NOTE:
* This does not work for access tokens where the audience is the Graph API - which introduces a
* "nonce" value into the header of the access token, making it all but impossible to verify the access
* token signature.
*/
const jwksClient = require("jwks-rsa");
const jwt = require("jsonwebtoken");
const fetch = require("node-fetch");
const getPem = require('rsa-pem-from-mod-exp');
const AADOID_JWKS_URL = "https://login.microsoftonline.com/<your tenant id>/discovery/v2.0/keys";
const accessTokenToVerify = "<access token to verify>";
const expectedTokenAud = "https://management.azure.com";
const expectedTokenIss = "https://sts.windows.net/<your tenant id>";
console.clear();
const displayToken = (token) => {
const decodedToken = jwt.decode(token, { complete: true });
console.log(`\ndecodedToken header:`, JSON.stringify(decodedToken.header, undefined, 3));
console.log(`\ndecodedToken payload:`, JSON.stringify(decodedToken.payload, undefined, 3));
console.log(`\ndecodedToken signature:`, JSON.stringify(decodedToken.signature, undefined, 3));
return Promise.resolve(decodedToken.header);
};
/** return the JSON web key set from the Azure AD OID configuration
response = {
keys: [
{
"kty": "RSA",
"use": "sig",
"kid": "nOo3ZDrODXEK1jKWhXslHR_KXEg",
"x5t": "nOo3ZDrODXEK1jKWhXslHR_KXEg",
"n": "oaLLT9hkcSj2tGfZsjbu7Xz1Krs0qEicXPmEsJKOBQHauZ_kRM1HdEkgOJbUznUspE6xOuOSXjlzErqBxXAu4SCvcvVOCYG2v9G3-uIrLF5dstD0sYHBo1VomtKxzF90Vslrkn6rNQgUGIWgvuQTxm1uRklYFPEcTIRw0LnYknzJ06GC9ljKR617wABVrZNkBuDgQKj37qcyxoaxIGdxEcmVFZXJyrxDgdXh9owRmZn6LIJlGjZ9m59emfuwnBnsIQG7DirJwe9SXrLXnexRQWqyzCdkYaOqkpKrsjuxUj2-MHX31FqsdpJJsOAvYXGOYBKJRjhGrGdONVrZdUdTBQ",
"e": "AQAB",
"x5c": [
"MIIDBTCCAe2gAwIBAgIQN33ROaIJ6bJBWDCxtmJEbjANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTIwMTIyMTIwNTAxN1oXDTI1MTIyMDIwNTAxN1owLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKGiy0/YZHEo9rRn2bI27u189Sq7NKhInFz5hLCSjgUB2rmf5ETNR3RJIDiW1M51LKROsTrjkl45cxK6gcVwLuEgr3L1TgmBtr/Rt/riKyxeXbLQ9LGBwaNVaJrSscxfdFbJa5J+qzUIFBiFoL7kE8ZtbkZJWBTxHEyEcNC52JJ8ydOhgvZYykete8AAVa2TZAbg4ECo9+6nMsaGsSBncRHJlRWVycq8Q4HV4faMEZmZ+iyCZRo2fZufXpn7sJwZ7CEBuw4qycHvUl6y153sUUFqsswnZGGjqpKSq7I7sVI9vjB199RarHaSSbDgL2FxjmASiUY4RqxnTjVa2XVHUwUCAwEAAaMhMB8wHQYDVR0OBBYEFI5mN5ftHloEDVNoIa8sQs7kJAeTMA0GCSqGSIb3DQEBCwUAA4IBAQBnaGnojxNgnV4+TCPZ9br4ox1nRn9tzY8b5pwKTW2McJTe0yEvrHyaItK8KbmeKJOBvASf+QwHkp+F2BAXzRiTl4Z+gNFQULPzsQWpmKlz6fIWhc7ksgpTkMK6AaTbwWYTfmpKnQw/KJm/6rboLDWYyKFpQcStu67RZ+aRvQz68Ev2ga5JsXlcOJ3gP/lE5WC1S0rjfabzdMOGP8qZQhXk4wBOgtFBaisDnbjV5pcIrjRPlhoCxvKgC/290nZ9/DLBH3TbHk8xwHXeBAnAjyAqOZij92uksAv7ZLq4MODcnQshVINXwsYshG1pQqOLwMertNaY5WtrubMRku44Dw7R"
],
"issuer": "https://login.microsoftonline.com/9b4f4fcf-59ca-4fb5-878f-4dea4046b5c6/v2.0"
}
]
}
*/
const getOIDJwksCollection = async (kid) => {
const response = await fetch(AADOID_JWKS_URL);
const body = await response.json();
const key = body.keys.find( (k) => (k.kid === kid) );
if (key === null) {
throw new Error(`Key ${kid} not found!`);
}
return key;
};
const verifyToken = async (token, tokenHeader, pemKey, x5cKey) => {
const client = jwksClient({
jwksUri: AADOID_JWKS_URL
});
console.log(`KID:`, tokenHeader.kid);
const key = await client.getSigningKey(tokenHeader.kid);
console.log(`JWKS KEY:`);
console.log(key);
const signingKey = key.publicKey || key.rsaPublicKey;
console.log(`Signing Key:`);
console.log(signingKey);
console.log(`\nVerify Token (1):`);
try {
const verify = await jwt.verify(token, signingKey, {
algorithms: ['RS256'],
issuer: `${expectedTokenIss}`,
audience: `${expectedTokenAud}`
});
console.log(verify);
} catch (e) {
console.error(e);
}
console.log(`\nVerify Token (2):`);
try {
const key1 = `-----BEGIN CERTIFICATE-----\n${x5cKey}\n-----END CERTIFICATE-----`;
const verify = await jwt.verify(token, key1, {
algorithms: ['RS256'],
issuer: `${expectedTokenIss}`,
audience: `${expectedTokenAud}`
});
console.log(verify);
} catch (e) {
console.error(e);
}
console.log(`\nVerify Token (3):`);
try {
const verify = await jwt.verify(token, pemKey, {
algorithms: ['RS256'],
issuer: `${expectedTokenIss}`,
audience: `${expectedTokenAud}`
});
console.log(verify);
} catch (e) {
console.error(e);
}
return Promise.resolve();
};
(async () => {
console.log("\nDISPLAY TOKEN\n======================================================");
const tokenHeader = await displayToken(accessTokenToVerify);
let pemKey = "";
let x5cKey = "";
try {
console.log("\nGET KEYS\n======================================================");
const key = await getOIDJwksCollection(tokenHeader.kid);
x5cKey = key.x5c;
pemKey = getPem(key.n, key.e); // pass in the key modulus (n) and exponent (e)
console.log(`PEM KEY for [${tokenHeader.kid}]\n`, pemKey);
console.log(`X5C KEY for [${tokenHeader.kid}]\n`, x5cKey);
} catch (e) {
console.error(e);
}
console.log("\n\nVERIFY TOKEN\n======================================================");
await verifyToken(accessTokenToVerify, tokenHeader, pemKey, x5cKey);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment