passport-azure-ad
Version:
OIDC and Bearer Passport strategies for Azure Active Directory
190 lines (158 loc) • 5.94 kB
JavaScript
/**
* Copyright (c) Microsoft Corporation
* All Rights Reserved
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the 'Software'), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
;
const request = require('request');
const async = require('async');
const aadutils = require('./aadutils');
const Log = require('./logging').getLogger;
const log = new Log('AzureAD: Metadata Parser');
function Metadata(url, authtype, options) {
if (!url) {
throw new Error('Metadata: url is a required argument');
}
if (!authtype || authtype !== 'oidc') {
throw new Error(`Invalid authtype. authtype must be 'oidc'`);
}
// if logging level specified, switch to it.
if (options.loggingLevel) { log.levels('console', options.loggingLevel); }
this.url = url;
this.metadata = null;
this.authtype = authtype;
}
Object.defineProperty(Metadata, 'url', {
get: function getUrl() {
return this.url;
},
});
Object.defineProperty(Metadata, 'oidc', {
get: function getOidc() {
return this.oidc;
},
});
Object.defineProperty(Metadata, 'metadata', {
get: function getMetadata() {
return this.metadata;
},
});
Metadata.prototype.updateOidcMetadata = function updateOidcMetadata(doc, next) {
log.info('Request to update the Open ID Connect Metadata');
const self = this;
var oidc = {};
oidc.algorithms = doc.id_token_signing_alg_values_supported;
oidc.authorization_endpoint = doc.authorization_endpoint;
oidc.end_session_endpoint = doc.end_session_endpoint;
oidc.issuer = doc.issuer;
oidc.token_endpoint = doc.token_endpoint;
oidc.userinfo_endpoint = doc.userinfo_endpoint;
self.oidc = oidc;
const jwksUri = doc.jwks_uri;
log.info('Algorithm retrieved was: ', self.oidc.algorithms);
log.info('Issuer we are using is: ', self.oidc.issuer);
log.info('Key Endpoint we will use is: ', jwksUri);
log.info('Authentication endpoint we will use is: ', self.oidc.authorization_endpoint);
log.info('Token endpoint we will use is: ', self.oidc.token_endpoint);
log.info('User info endpoint we will use is: ', self.oidc.userinfo_endpoint);
log.info('The logout endpoint we will use is: ', self.oidc.end_session_endpoint);
// fetch the signing keys
request.get(jwksUri, { json: true }, (err, response, body) => {
if (err) {
return next(err);
}
if (response.statusCode !== 200) {
return next(new Error(`Error: ${response.statusCode} Cannot get AAD Signing Keys`));
}
self.oidc.keys = body.keys;
return next();
});
};
Metadata.prototype.generateOidcPEM = function generateOidcPEM(kid) {
const keys = this && this.oidc && Array.isArray(this.oidc.keys) ? this.oidc.keys : null;
let pubKey = null;
let foundKey = false;
if (!kid) {
throw new Error('kid is missing');
}
if (!keys) {
throw new Error('keys is missing');
}
keys.some((key) => {
log.info('working on key:', key);
// are we working on the right key?
if (key.kid !== kid) {
return false;
}
// check for `modulus` to be present
if (!key.n) {
log.warn('modulus is empty; corrupt key', key);
return false;
}
// check for `exponent` to be present
if (!key.e) {
log.warn('exponent is empty; corrupt key', key);
return false;
}
// generate PEM from `modulus` and `exponent`
pubKey = aadutils.rsaPublicKeyPem(key.n, key.e);
foundKey = true;
return pubKey;
});
if (!foundKey)
throw new Error(`a key with kid %s cannot be found`, kid);
if (!pubKey)
throw new Error(`generating public key pem failed for kid: %s`, kid);
return pubKey;
};
Metadata.prototype.fetch = function fetch(callback) {
const self = this;
async.waterfall([
// fetch the Federation metadata for the AAD tenant
(next) => {
request.get(self.url, (err, response, body) => {
if (err) {
return next(err);
}
if (response.statusCode !== 200) {
log.error('Cannot get AAD Federation metadata from endpoint you specified', self.url);
return next(new Error(`Error: ${response.statusCode} Cannot get AAD Federation metadata
from ${self.url}`));
}
return next(null, body);
});
},
// parse retrieved metadata
(body, next) => {
// use json parser for oidc authType
log.info('Parsing JSON retreived from the endpoint');
self.metadata = JSON.parse(body);
return next(null);
},
// call update method for parsed metadata and authType
(next) => {
return self.updateOidcMetadata(self.metadata, next);
},
], (waterfallError) => {
// return err or success (err === null) to callback
callback(waterfallError);
});
};
exports.Metadata = Metadata;