UNPKG

azure-ad-jwt

Version:

An Azure Active Directory Token Validation component for node.js

171 lines (141 loc) 5.42 kB
var jsonwebtoken = require('jsonwebtoken'); var request = require('request'); var cache = require('./azure-ad-cache'); function AzureActiveDirectoryValidationManager() { var self = this; function convertCertificateToBeOpenSSLCompatible(cert) { //Certificate must be in this specific format or else the function won't accept it var beginCert = "-----BEGIN CERTIFICATE-----"; var endCert = "-----END CERTIFICATE-----"; cert = cert.replace("\n", ""); cert = cert.replace(beginCert, ""); cert = cert.replace(endCert, ""); var result = beginCert; while (cert.length > 0) { if (cert.length > 64) { result += "\n" + cert.substring(0, 64); cert = cert.substring(64, cert.length); } else { result += "\n" + cert; cert = ""; } } if (result[result.length ] != "\n") result += "\n"; result += endCert + "\n"; return result; } /* * Extracts the tenant id from the give jwt token */ self.getTenantId = function(jwtString) { var decodedToken = jsonwebtoken.decode(jwtString); if (decodedToken) { return decodedToken.tid; } else { return null; } }; /* * This function loads the open-id configuration for a specific AAD tenant * from a well known application. */ self.requestOpenIdConfig = function(tenantId, cb) { // we need to load the tenant specific open id config var tenantOpenIdconfig = { url: 'https://login.windows.net/' + tenantId + '/.well-known/openid-configuration', json: true }; var cachedValue = cache.get(tenantOpenIdconfig); if (cachedValue) return cb(null, cachedValue); request.get(tenantOpenIdconfig, function(error, response, result) { if (error) { cb(error); } else { cache.put(tenantOpenIdconfig, result); cb(null, result); } }); }; /* * Download the signing certificates which is the public portion of the * keys used to sign the JWT token. Signature updated to include options for the kid. */ self.requestSigningCertificates = function(jwtSigningKeysLocation, options, cb) { var jwtSigningKeyRequestOptions = { url: jwtSigningKeysLocation, json: true }; var cachedValue = cache.get(jwtSigningKeysLocation); if (cachedValue) return cb(null, cachedValue); request.get(jwtSigningKeyRequestOptions, function(error, response, result) { if (error) { cb(error); } else { var certificates = []; //Use KID to locate the public key and store the certificate chain. if (options && options.kid) { result.keys.find(function(publicKey) { if (publicKey.kid === options.kid) { publicKey.x5c.forEach(function(certificate) { certificates.push(convertCertificateToBeOpenSSLCompatible(certificate)); }); } }) } else { result.keys.forEach(function(publicKeys) { publicKeys.x5c.forEach(function(certificate) { certificates.push(convertCertificateToBeOpenSSLCompatible(certificate)); }) }); } // good to go cache.put(jwtSigningKeysLocation, certificates); cb(null, certificates); } }); }; /* * This function tries to verify the token with every certificate until * all certificates was testes or the first one matches. After that the token is valid */ self.verify = function(jwt, certificates, options, cb) { // ensure we have options if (!options) options = {}; // set the correct algorithm options.algorithms = ['RS256']; // set the issuer we expect options.issuer = 'https://sts.windows.net/' + self.getTenantId(jwt) + '/'; var valid = false; var lastError = null; certificates.every(function(certificate) { // verify the token try { // verify the token jsonwebtoken.verify(jwt, certificate, options); // set the state valid = true; lastError = null; // abort the enumeration return false; } catch(error) { // set teh error state lastError = error; // check if we should try the next certificate if (error.message === 'invalid signature') { return true; } else { return false; } } }); // done if (valid) { cb(null, jsonwebtoken.decode(jwt)); } else { cb(lastError, null); } } } module.exports = exports = AzureActiveDirectoryValidationManager;