function Strategy(options, verifyFn) { passport.Strategy.call(this); this.name = 'oauth-bearer'; // Me, a name I call myself. if (!options) throw new Error('In BearerStrategy constructor: options is required'); if (!verifyFn || typeof verifyFn !== 'function') throw new Error('In BearerStrategy constructor: verifyFn is required and it must be a function'); this._verify = verifyFn; this._options = options; //--------------------------------------------------------------------------- // Set up the default values //--------------------------------------------------------------------------- // clock skew. Must be a postive integer if (options.clockSkew && (typeof options.clockSkew !== 'number' || options.clockSkew <= 0 || options.clockSkew % 1 !== 0)) throw new Error('clockSkew must be a positive integer'); if (!options.clockSkew) options.clockSkew = CONSTANTS.CLOCK_SKEW; // default value of passReqToCallback is false if (options.passReqToCallback !== true) options.passReqToCallback = false; // default value of validateIssuer is true if (options.validateIssuer !== false) options.validateIssuer = true; // default value of allowMultiAudiencesInToken is false if (options.allowMultiAudiencesInToken !== true) options.allowMultiAudiencesInToken = false; // if options.audience is a string or an array of string, then we use it; // otherwise we use the clientID if (options.audience && typeof options.audience === 'string') options.audience = [options.audience]; else if (!options.audience || !Array.isArray(options.audience) || options.length === 0) options.audience = [options.clientID, 'spn:' + options.clientID]; // default value of isB2C is false if (options.isB2C !== true) options.isB2C = false; // turn issuer into an array if (options.issuer === '') options.issuer = null; if (options.issuer && Array.isArray(options.issuer) && options.issuer.length === 0) options.issuer = null; if (options.issuer && !Array.isArray(options.issuer)) options.issuer = [options.issuer]; //--------------------------------------------------------------------------- // validate the things in options //--------------------------------------------------------------------------- // clientID should not be empty if (!options.clientID || options.clientID === '') throw new Error('In BearerStrategy constructor: clientID cannot be empty'); // identityMetadata must be https url if (!options.identityMetadata || !UrlValidator.isHttpsUri(options.identityMetadata)) throw new Error('In BearerStrategy constructor: identityMetadata must be provided and must be a https url'); //--------------------------------------------------------------------------- // treatment of common endpoint and issuer //--------------------------------------------------------------------------- // check if we are using the common endpoint options._isCommonEndpoint = (options.identityMetadata.indexOf('/common/') != -1); // issuer validation for common endpoint is not supported if (options._isCommonEndpoint && options.validateIssuer && !options.issuer) throw new Error(`In BearerStrategy constructor: you are using common endpoint, please either set 'validateIssuer' to false, or provide 'issuer' value in options`); // give a warning if user is not validating issuer if (!options.validateIssuer) log.warn(`Production environments should always validate the issuer.`); //--------------------------------------------------------------------------- // B2C. // (1) policy must be provided and must have the valid prefix // (2) common endpoint is not supported //--------------------------------------------------------------------------- // for B2C, if (options.isB2C) { if (!options.policyName || !options.policyName.toLowerCase().startsWith(B2C_PREFIX)) throw new Error('In BearerStrategy constructor: invalid policy for B2C'); ...
n/a
function Strategy() { }
n/a
function Strategy(options, verify) { passport.Strategy.call(this); /* * Caution when you want to change these values in the member functions of * Strategy, don't use `this`, since `this` points to a subclass of `Strategy`. * To get `Strategy`, use Object.getPrototypeOf(this). * * More comments at the beginning of `Strategy.prototype.authenticate`. */ this._options = options; this.name = 'azuread-openidconnect'; // stuff related to the verify function this._verify = verify; this._passReqToCallback = !!options.passReqToCallback; if (options.useCookieInsteadOfSession === true) this._useCookieInsteadOfSession = true; else this._useCookieInsteadOfSession = false; if (!this._useCookieInsteadOfSession) { // For each microsoft login page tab, we generate a {state, nonce, policy, timeStamp} tuple, // normally user won't keep opening microsoft login page in new tabs without putting their // password for more than 10 tabs, so we only keep the most recent 10 tuples in session. // The lifetime of each tuple is 60 minutes or user specified. this._sessionContentHandler = new SessionContentHandler(options.nonceMaxAmount || NONCE_MAX_AMOUNT, options.nonceLifetime || NONCE_LIFE_TIME); } else { this._cookieContentHandler = new CookieContentHandler(options.nonceMaxAmount || NONCE_MAX_AMOUNT, options.nonceLifetime || NONCE_LIFE_TIME , options.cookieEncryptionKeys); } /* When a user is authenticated for the first time, passport adds a new field * to req.session called 'passport', and puts a 'user' property inside (or your * choice of field name and property name if you change passport._key and * passport._userProperty values). req.session['passport']['user'] is usually * user_id (or something similar) of the authenticated user to reduce the size * of session. When the user logs out, req.session['passport']['user'] will be * destroyed. Any request between login (when authenticated for the first time) * and logout will have the 'user_id' in req.session['passport']['user'], so * passport can fetch it, find the user object in database and put the user * object into a new field: req.user. Then the subsequent middlewares and the * app can use the user object. This is how passport keeps user authenticated. * * For state validation, we also take advantage of req.session. we create a new * field: req.session[sessionKey], where the sessionKey is our choice (in fact, * this._key, see below). When we send a request with state, we put state into * req.session[sessionKey].state; when we expect a request with state in query * or body, we compare the state in query/body with the one stored in * req.session[sessionKey].state, and then destroy req.session[sessionKey].state. * User can provide a state by using `authenticate(Strategy, {state: 'xxx'})`, or * one will be generated automatically. This is essentially how passport-oauth2 * library does the state validation, and we use the same way in our library. * * request structure will look like the following. In real request some fields * might not be there depending on the purpose of the request. * * request ---|--- sessionID * |--- session --- |--- ... * | |--- 'passport' ---| --- 'user': 'user_id etc' * | |--- sessionKey---| --- state: 'xxx' * |--- ... * |--- 'user': full user info */ this._key = options.sessionKey || ('OIDC: ' + options.redirectUrl); if (!options.identityMetadata) { // default value should be https://login.microsoftonline.com/common/.well-known/openid-configuration log.error('OIDCStrategy requires a metadata location that contains cert data for RSA and ECDSA callback.'); throw new TypeError(`OIDCStrategy requires a metadata location that contains cert data for RSA and ECDSA callback.`); } // if loggi ...
n/a
function Strategy(options, verifyFn) { passport.Strategy.call(this); this.name = 'oauth-bearer'; // Me, a name I call myself. if (!options) throw new Error('In BearerStrategy constructor: options is required'); if (!verifyFn || typeof verifyFn !== 'function') throw new Error('In BearerStrategy constructor: verifyFn is required and it must be a function'); this._verify = verifyFn; this._options = options; //--------------------------------------------------------------------------- // Set up the default values //--------------------------------------------------------------------------- // clock skew. Must be a postive integer if (options.clockSkew && (typeof options.clockSkew !== 'number' || options.clockSkew <= 0 || options.clockSkew % 1 !== 0)) throw new Error('clockSkew must be a positive integer'); if (!options.clockSkew) options.clockSkew = CONSTANTS.CLOCK_SKEW; // default value of passReqToCallback is false if (options.passReqToCallback !== true) options.passReqToCallback = false; // default value of validateIssuer is true if (options.validateIssuer !== false) options.validateIssuer = true; // default value of allowMultiAudiencesInToken is false if (options.allowMultiAudiencesInToken !== true) options.allowMultiAudiencesInToken = false; // if options.audience is a string or an array of string, then we use it; // otherwise we use the clientID if (options.audience && typeof options.audience === 'string') options.audience = [options.audience]; else if (!options.audience || !Array.isArray(options.audience) || options.length === 0) options.audience = [options.clientID, 'spn:' + options.clientID]; // default value of isB2C is false if (options.isB2C !== true) options.isB2C = false; // turn issuer into an array if (options.issuer === '') options.issuer = null; if (options.issuer && Array.isArray(options.issuer) && options.issuer.length === 0) options.issuer = null; if (options.issuer && !Array.isArray(options.issuer)) options.issuer = [options.issuer]; //--------------------------------------------------------------------------- // validate the things in options //--------------------------------------------------------------------------- // clientID should not be empty if (!options.clientID || options.clientID === '') throw new Error('In BearerStrategy constructor: clientID cannot be empty'); // identityMetadata must be https url if (!options.identityMetadata || !UrlValidator.isHttpsUri(options.identityMetadata)) throw new Error('In BearerStrategy constructor: identityMetadata must be provided and must be a https url'); //--------------------------------------------------------------------------- // treatment of common endpoint and issuer //--------------------------------------------------------------------------- // check if we are using the common endpoint options._isCommonEndpoint = (options.identityMetadata.indexOf('/common/') != -1); // issuer validation for common endpoint is not supported if (options._isCommonEndpoint && options.validateIssuer && !options.issuer) throw new Error(`In BearerStrategy constructor: you are using common endpoint, please either set 'validateIssuer' to false, or provide 'issuer' value in options`); // give a warning if user is not validating issuer if (!options.validateIssuer) log.warn(`Production environments should always validate the issuer.`); //--------------------------------------------------------------------------- // B2C. // (1) policy must be provided and must have the valid prefix // (2) common endpoint is not supported //--------------------------------------------------------------------------- // for B2C, if (options.isB2C) { if (!options.policyName || !options.policyName.toLowerCase().startsWith(B2C_PREFIX)) throw new Error('In BearerStrategy constructor: invalid policy for B2C'); ...
n/a
function Strategy() { }
n/a
function authenticateStrategy(req) { const self = this; var params = {}; var optionsToValidate = {}; /* Some introduction to async.waterfall (from the following link): * http://stackoverflow.com/questions/28908180/what-is-a-simple-implementation-of-async-waterfall * * Runs the tasks array of functions in series, each passing their results * to the next in the array. However, if any of the tasks pass an error to * their own callback, the next function is not executed, and the main callback * is immediately called with the error. * * Example: * * async.waterfall([ * function(callback) { * callback(null, 'one', 'two'); * }, * function(arg1, arg2, callback) { * // arg1 now equals 'one' and arg2 now equals 'two' * callback(null, 'three'); * }, * function(arg1, callback) { * // arg1 now equals 'three' * callback(null, 'done'); * } * ], function (err, result) { * // result now equals 'done' * }); */ async.waterfall([ // compute metadataUrl (next) => { params.metadataURL = self._options.identityMetadata .concat(`?${aadutils.getLibraryProductParameterName()}=${aadutils.getLibraryProduct()}`) .concat(`&${aadutils.getLibraryVersionParameterName()}=${aadutils.getLibraryVersion()}`); ; if (self._options.isB2C) params.metadataURL = params.metadataURL.concat(`&p=${self._options.policyName}`) params.cacheKey = params.metadataURL; log.info(`In Strategy.prototype.authenticate: ${JSON.stringify(params)}`); return next(null, params); }, // load metatadata (params, next) => { return self.loadMetadata(params, next); }, // configure using metadata (metadata, next) => { params.metadata = metadata; log.info(`In Strategy.prototype.authenticate: received metadata: ${JSON.stringify(metadata)}`); // set up issuer if (self._options.validateIssuer && !self._options.issuer) optionsToValidate.issuer = metadata.oidc.issuer; else optionsToValidate.issuer = self._options.issuer; // set up algorithm optionsToValidate.algorithms = metadata.oidc.algorithms; // set up audience, validateIssuer, allowMultiAudiencesInToken optionsToValidate.audience = self._options.audience; optionsToValidate.validateIssuer = self._options.validateIssuer; optionsToValidate.allowMultiAudiencesInToken = self._options.allowMultiAudiencesInToken; optionsToValidate.ignoreExpiration = self._options.ignoreExpiration; // clock skew optionsToValidate.clockSkew = self._options.clockSkew; log.info(`In Strategy.prototype.authenticate: we will validate the following options: ${optionsToValidate}`); return next(); }, // extract the access token from the request, after getting the token, it // will call `jwtVerify` to verify the token. If token is verified, `jwtVerify` // will provide the token payload to self._verify function. self._verify is // provided by the developer, it's up to the developer to decide if the token // payload is considered authenticated. If authenticated, self._verify will // provide `user` object (developer's decision of its content) to `verified` // function here, and the `verified` function does the final work of stuffing // the `user` obejct into req.user, so the following middleware can use it. // This is basically how bearerStrategy works. (next) => { var token; // token could be in header or body. query is not supported. if (req.query && req.query.access_token) return self.failWithLog('In Strategy.prototype.authenticate: access_token should be passed in request header or body. query is unsupported'); if (req.headers && req.headers.authorization) { var auth_components = req.headers.authorization.split(' '); if (auth_ ...
...
To complete the sample, provide a route that corresponds to the path
configuration parameter that is sent to the strategy:
```javascript
app.get('/login',
passport.authenticate('azuread-openidconnect', { failureRedirect: '/
x27; }),
function(req, res) {
log.info('Login was called in the Sample');
res.redirect('/');
});
// POST /auth/openid/return
// Use passport.authenticate() as route middleware to authenticate the
...
failWithLog = function (message) { log.info(`authentication failed due to: ${message}`); return this.fail(message); }
n/a
function jwtVerifyFunc(req, token, metadata, optionsToValidate, done) { const self = this; const decoded = jws.decode(token); let PEMkey = null; if (decoded == null) { return done(null, false, 'In Strategy.prototype.jwtVerify: Invalid JWT token.'); } log.info('In Strategy.prototype.jwtVerify: token decoded: ', decoded); // When we generate the PEMkey, there are two different types of token signatures // we have to validate here. One provides x5t and the other a kid. We need to call // the right one. if (decoded.header.x5t) { PEMkey = metadata.generateOidcPEM(decoded.header.x5t); } else if (decoded.header.kid) { PEMkey = metadata.generateOidcPEM(decoded.header.kid); } else { return self.failWithLog('In Strategy.prototype.jwtVerify: We did not receive a token we know how to validate'); } log.info('PEMkey generated: ' + PEMkey); jwt.verify(token, PEMkey, optionsToValidate, (err, verifiedToken) => { if (err) { if (err.message) return self.failWithLog(err.message); else return self.failWithLog('In Strategy.prototype.jwtVerify: cannot verify id token'); } log.info('In Strategy.prototype.jwtVerify: VerifiedToken: ', verifiedToken); if (self._options.passReqToCallback) { log.info('In Strategy.prototype.jwtVerify: We did pass Req back to Callback'); return self._verify(req, verifiedToken, done); } else { log.info('In Strategy.prototype.jwtVerify: We did not pass Req back to Callback'); return self._verify(verifiedToken, done); } }); }
n/a
loadMetadata = function (params, next) { const self = this; var metadata = new Metadata(params.metadataURL, 'oidc', self._options); // fetch metadata return memoryCache.wrap(params.cacheKey, (cacheCallback) => { metadata.fetch((fetchMetadataError) => { if (fetchMetadataError) { return self.failWithLog('In loadMetadata: Unable to fetch metadata'); } return cacheCallback(null, metadata); }); }, { ttl }, next); }
n/a
function Strategy() { }
n/a
function Strategy() { }
n/a
authenticate = function (req, options) { throw new Error('Strategy#authenticate must be overridden by subclass'); }
...
To complete the sample, provide a route that corresponds to the path
configuration parameter that is sent to the strategy:
```javascript
app.get('/login',
passport.authenticate('azuread-openidconnect', { failureRedirect: '/
x27; }),
function(req, res) {
log.info('Login was called in the Sample');
res.redirect('/');
});
// POST /auth/openid/return
// Use passport.authenticate() as route middleware to authenticate the
...
function Strategy(options, verify) { passport.Strategy.call(this); /* * Caution when you want to change these values in the member functions of * Strategy, don't use `this`, since `this` points to a subclass of `Strategy`. * To get `Strategy`, use Object.getPrototypeOf(this). * * More comments at the beginning of `Strategy.prototype.authenticate`. */ this._options = options; this.name = 'azuread-openidconnect'; // stuff related to the verify function this._verify = verify; this._passReqToCallback = !!options.passReqToCallback; if (options.useCookieInsteadOfSession === true) this._useCookieInsteadOfSession = true; else this._useCookieInsteadOfSession = false; if (!this._useCookieInsteadOfSession) { // For each microsoft login page tab, we generate a {state, nonce, policy, timeStamp} tuple, // normally user won't keep opening microsoft login page in new tabs without putting their // password for more than 10 tabs, so we only keep the most recent 10 tuples in session. // The lifetime of each tuple is 60 minutes or user specified. this._sessionContentHandler = new SessionContentHandler(options.nonceMaxAmount || NONCE_MAX_AMOUNT, options.nonceLifetime || NONCE_LIFE_TIME); } else { this._cookieContentHandler = new CookieContentHandler(options.nonceMaxAmount || NONCE_MAX_AMOUNT, options.nonceLifetime || NONCE_LIFE_TIME , options.cookieEncryptionKeys); } /* When a user is authenticated for the first time, passport adds a new field * to req.session called 'passport', and puts a 'user' property inside (or your * choice of field name and property name if you change passport._key and * passport._userProperty values). req.session['passport']['user'] is usually * user_id (or something similar) of the authenticated user to reduce the size * of session. When the user logs out, req.session['passport']['user'] will be * destroyed. Any request between login (when authenticated for the first time) * and logout will have the 'user_id' in req.session['passport']['user'], so * passport can fetch it, find the user object in database and put the user * object into a new field: req.user. Then the subsequent middlewares and the * app can use the user object. This is how passport keeps user authenticated. * * For state validation, we also take advantage of req.session. we create a new * field: req.session[sessionKey], where the sessionKey is our choice (in fact, * this._key, see below). When we send a request with state, we put state into * req.session[sessionKey].state; when we expect a request with state in query * or body, we compare the state in query/body with the one stored in * req.session[sessionKey].state, and then destroy req.session[sessionKey].state. * User can provide a state by using `authenticate(Strategy, {state: 'xxx'})`, or * one will be generated automatically. This is essentially how passport-oauth2 * library does the state validation, and we use the same way in our library. * * request structure will look like the following. In real request some fields * might not be there depending on the purpose of the request. * * request ---|--- sessionID * |--- session --- |--- ... * | |--- 'passport' ---| --- 'user': 'user_id etc' * | |--- sessionKey---| --- state: 'xxx' * |--- ... * |--- 'user': full user info */ this._key = options.sessionKey || ('OIDC: ' + options.redirectUrl); if (!options.identityMetadata) { // default value should be https://login.microsoftonline.com/common/.well-known/openid-configuration log.error('OIDCStrategy requires a metadata location that contains cert data for RSA and ECDSA callback.'); throw new TypeError(`OIDCStrategy requires a metadata location that contains cert data for RSA and ECDSA callback.`); } // if loggi ...
n/a
function Strategy() { }
n/a
function authCodeFlowHandler(params, oauthConfig, optionsToValidate, req, next, iss, sub) { /* we will do the following things in order: * (1) use code to get id_token and access_token * (2) validate the id_token and the access_token received * (3) if user asks for userinfo and we are using AAD v1, then we use access_token to get * userinfo, then make sure the userinfo has the same 'sub' as that in the 'id_token' */ const self = this; var code = params.code; log.info('entering Strategy.prototype._authCodeFlowHandler, received code: ' + code); var issFromPrevIdToken = iss; var subFromPrevIdToken = sub; return self._getAccessTokenBySecretOrAssertion(code, oauthConfig, next, (getOAuthAccessTokenError, items) => { if (getOAuthAccessTokenError) return next(new Error(`In _authCodeFlowHandler: failed to redeem authorization code: ${aadutils.getErrorMessage(getOAuthAccessTokenError )}`)); var access_token = items.access_token; var refresh_token = items.refresh_token; // id_token should be present if (!items.id_token) return next(new Error('In _authCodeFlowHandler: id_token is not received')); // token_type must be 'Bearer' ignoring the case if (items.token_type.toLowerCase() !== 'bearer') { log.info('token_type received is: ', items.token_type); return next(new Error(`In _authCodeFlowHandler: token_type received is not 'Bearer' ignoring the case`)); } log.info('received id_token: ', items.id_token); log.info('received access_token: ', access_token); log.info('received refresh_token: ', refresh_token); // validate id_token and access_token, so put them into params params.access_token = access_token; params.id_token = items.id_token; return self._idTokenHandler(params, optionsToValidate, req, next, (jwtClaimsStr, jwtClaims) => { // for 'code id_token' flow, check iss/sub in the id_token from the authorization endpoint // with those in the id_token from token endpoint if (issFromPrevIdToken && issFromPrevIdToken !== jwtClaims.iss) return next(new Error('In _authCodeFlowHandler: After redeeming the code, iss in id_token from authorize_endpoint does not match iss in id_token from token_endpoint')); if (subFromPrevIdToken && subFromPrevIdToken !== jwtClaims.sub) return next(new Error('In _authCodeFlowHandler: After redeeming the code, sub in id_token from authorize_endpoint does not match sub in id_token from token_endpoint')); const sub = jwtClaims.sub; const iss = jwtClaims.iss; // load the userinfo, if this is not v2 and if we don't specify the resource // for v1 if we don't specify the resource, then the access_token we got is for userinfo endpoint, so we can use it to get userinfo if (!self._options._isV2 && !params.resource) { // make sure we get an access_token if (!access_token) return next(new Error("In _authCodeFlowHandler: we want to access userinfo endpoint, but access_token is not received")); let parsedUrl; try { parsedUrl = url.parse(oauthConfig.userinfo_endpoint, true); } catch (urlParseException) { return next(new Error(`In _authCodeFlowHandler: Failed to parse config property 'userInfoURL' with value ${oauthConfig .userinfo_endpoint}`)); } parsedUrl.query.schema = 'openid'; delete parsedUrl.search; // delete operations are slow; should we rather just overwrite it with {} const userInfoURL = url.format(parsedUrl); // ask oauth2 to use authorization header to bearer access token var oauth2 = createOauth2Instance(oauthConfig); oauth2.useAuthorizationHeaderforGET(true); return oauth2.get(userInfoURL, access_token, (getUserInfoError, body) => { if (getUserInfoError) return next(new Error(`In _authCodeFlowHandler: failed to fetch user profile: ${aadutils.getErrorMessage(getUserInfoError )}`)); ...
n/a
function errorResponseHandler(err, err_description, next) { const self = this; log.info('Error received in the response was: ', err); if (err_description) log.info('Error description received in the response was: ', err_description); // Unfortunately, we cannot return the 'error description' to the user, since // it goes to http header by default and it usually contains characters that // http header doesn't like, which causes the program to crash. return next(new Error(err)); }
n/a
function flowInitializationHandler(oauthConfig, req, next) { // The request being authenticated is initiating OpenID Connect // authentication. Prior to redirecting to the provider, configuration will // be loaded. The configuration is typically either pre-configured or // discovered dynamically. When using dynamic discovery, a user supplies // their identifer as input. const self = this; log.info(`entering Strategy.prototype._flowInitializationHandler`); const params = { 'redirect_uri': oauthConfig.redirectUrl, 'response_type': oauthConfig.responseType, 'response_mode': oauthConfig.responseMode, 'client_id': oauthConfig.clientID }; log.info('We are sending the response_type: ', params.response_type); log.info('We are sending the response_mode: ', params.response_mode); if (oauthConfig.domain_hint) params['domain_hint'] = oauthConfig.domain_hint; if (oauthConfig.login_hint) params['login_hint'] = oauthConfig.login_hint; if (oauthConfig.prompt) params['prompt'] = oauthConfig.prompt; if (oauthConfig.extraAuthReqQueryParams) { // copy the extra query parameters into params for (var attributeName in oauthConfig.extraAuthReqQueryParams) { if (oauthConfig.extraAuthReqQueryParams.hasOwnProperty(attributeName)) params[attributeName] = oauthConfig.extraAuthReqQueryParams[attributeName]; } } var policy = null; if (self._options.isB2C) { if (!req.query.p || req.query.p === '') return next(new Error('In _flowInitializationHandler: missing policy in the request for B2C')); // policy is not case sensitive. AAD turns policy to lower case. policy = req.query.p.toLowerCase(); if (!policy || !CONSTANTS.POLICY_REGEX.test(policy)) return next(new Error(`In _flowInitializationHandler: the given policy ${policy} given in the request is invalid`)); } // add state/nonce/policy/timeStamp tuple to session or cookie let state = params.state = oauthConfig.customState ? ('CUSTOM' + aadutils.uid(32) + oauthConfig.customState) : aadutils.uid(32 ); let nonce = params.nonce = aadutils.uid(32); let resource = oauthConfig.resource ? oauthConfig.resource : null; if (resource) params.resource = resource; var tuple = { state: state, nonce: nonce }; if (policy) tuple.policy = policy; if (resource) tuple.resource = resource; if (oauthConfig.tenantIdOrName) tuple.tenantIdOrName = oauthConfig.tenantIdOrName; if (!self._useCookieInsteadOfSession) { tuple.timeStamp = Date.now(); self._sessionContentHandler.add(req, self._key, tuple); } else { // we don't need to record timestamp if we use cookie, since we can set the // expiration when we set cookie self._cookieContentHandler.add(req, oauthConfig.response, tuple); } // add scope params.scope = oauthConfig.scope; // add telemetry params[aadutils.getLibraryProductParameterName()] = aadutils.getLibraryProduct(); params[aadutils.getLibraryVersionParameterName()] = aadutils.getLibraryVersion(); let location; // Implement support for standard OpenID Connect params (display, prompt, etc.) if (self._options.isB2C) location = `${oauthConfig.authorization_endpoint}&${querystring.stringify(params)}`; else location = `${oauthConfig.authorization_endpoint}?${querystring.stringify(params)}`; return self.redirect(location); }
n/a
(code, oauthConfig, next, callback) => { var post_headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; var post_params = { code: code, client_id: oauthConfig.clientID, redirect_uri: oauthConfig.redirectUrl, scope: oauthConfig.scope, grant_type: 'authorization_code' }; if (oauthConfig.extraTokenReqQueryParams) { // copy the extra query parameters into post_params for (var attributeName in oauthConfig.extraTokenReqQueryParams) { if (oauthConfig.extraTokenReqQueryParams.hasOwnProperty(attributeName)) post_params[attributeName] = oauthConfig.extraTokenReqQueryParams[attributeName]; } } if (oauthConfig.clientSecret) { // use client secret if it exists post_params['client_secret'] = oauthConfig.clientSecret; log.info('In _getAccessTokenBySecretOrAssertion: we are using client secret'); } else { // otherwise generate a client assertion post_params['client_assertion_type'] = CONSTANTS.CLIENT_ASSERTION_TYPE; jwt.generateClientAssertion(oauthConfig.clientID, oauthConfig.token_endpoint, oauthConfig.privatePEMKey, oauthConfig.thumbprint , (err, assertion) => { if (err) return next(err); else post_params['client_assertion'] = assertion; }); log.info('In _getAccessTokenBySecretOrAssertion: we created a client assertion with thumbprint ' + oauthConfig.thumbprint); }; var post_data = querystring.stringify(post_params); var oauth2 = createOauth2Instance(oauthConfig); oauth2._request('POST', oauthConfig.token_endpoint, post_headers, post_data, null, (error, data, response) => { if (error) callback(error); else { var results; try { results = JSON.parse(data); } catch(e) { results = querystring.parse(data); } callback(null, results); } }); }
n/a
function hybridFlowHandler(params, oauthConfig, optionsToValidate, req, next) { /* we will do the following things in order * (1) validate the id_token and the code * (2) if there is no userinfo token needed (or ignored if using AAD v2 ), we use * the claims in id_token for user's profile * (3) if userinfo token is needed, we will use the 'code' and the authorization code flow */ const self = this; log.info('entering Strategy.prototype._hybridFlowHandler, received code: ' + params.code + ', received id_token: ' + params.id_token ); // save nonce, since if we use the authorization code flow later, we have to check // nonce again. // validate the id_token and the code return self._idTokenHandler(params, optionsToValidate, req, next, (jwtClaimsStr, jwtClaims) => { // c_hash is required for 'code id_token' flow. If we have c_hash, then _validateResponse already // validates it; otherwise, _validateResponse ignores the c_hash check, and we check here if (!jwtClaims.c_hash) return next(new Error("In _hybridFlowHandler: we are in hybrid flow using code id_token, but c_hash is not found in id_token ")); const sub = jwtClaims.sub; const iss = jwtClaims.iss; // now we use the authorization code flow return self._authCodeFlowHandler(params, oauthConfig, optionsToValidate, req, next, iss, sub); }); }
n/a
function idTokenHandler(params, optionsToValidate, req, next, callback) { const self = this; var id_token = params.id_token; var parts = id_token.split('.'); if (parts.length === 3) return self._validateResponse(params, optionsToValidate, req, next, callback); else if (parts.length === 5) { log.info('In _idTokenHandler: we received an id_token of JWE format, we are decrypting it'); var decrypted_token; return jwe.decrypt(id_token, optionsToValidate.jweKeyStore, log, (err, decrypted_token) => { if(err) return next(err); params.id_token = decrypted_token; return self._validateResponse(params, optionsToValidate, req, next, callback); }); } else return next(new Error(`id_token has ${parts.length} parts, it is neither jwe nor jws`)); }
n/a
function implicitFlowHandler(params, optionsToValidate, req, next) { /* we will do the following things in order * (1) validate id_token * (2) use the claims in the id_token for user's profile */ const self = this; log.info('entering Strategy.prototype._implicitFlowHandler, received id_token: ' + params.id_token); // validate the id_token return self._idTokenHandler(params, optionsToValidate, req, next, (jwtClaimsStr, jwtClaims) => { const sub = jwtClaims.sub; const iss = jwtClaims.iss; log.info('we are in implicit flow, use the content in id_token as the profile'); return onProfileLoaded(self, { req, sub, iss, profile: makeProfileObject(jwtClaims, jwtClaimsStr), jwtClaims, access_token: null, refresh_token: null, params: null }); }); }
n/a
function validateResponse(params, optionsToValidate, req, next, callback) { const self = this; var id_token = params.id_token; var code = params.code; var access_token = params.access_token; // decode id_token const decoded = jws.decode(id_token); if (decoded == null) return next(new Error('In _validateResponse: Invalid JWT token')); log.info('token decoded: ', decoded); // get Pem Key var PEMkey = null; if (decoded.header.kid) { PEMkey = params.metadata.generateOidcPEM(decoded.header.kid); } else if (decoded.header.x5t) { PEMkey = params.metadata.generateOidcPEM(decoded.header.x5t); } else { return next(new Error('In _validateResponse: We did not receive a token we know how to validate')); } log.info('PEMkey generated: ' + PEMkey); // verify id_token signature and claims return jwt.verify(id_token, PEMkey, optionsToValidate, (err, jwtClaims) => { if (err) return next(new Error(`In _validateResponse: ${aadutils.getErrorMessage(err)}`)); log.info("Claims received: ", jwtClaims); // jwt checks the 'nbf', 'exp', 'aud', 'iss' claims // there are a few other things we will check below // For B2C, check the policy if (self._options.isB2C) { var policy_in_idToken; if (jwtClaims.acr && CONSTANTS.POLICY_REGEX.test(jwtClaims.acr)) policy_in_idToken = jwtClaims.acr; else if (jwtClaims.tfp && CONSTANTS.POLICY_REGEX.test(jwtClaims.tfp)) policy_in_idToken = jwtClaims.tfp.toLowerCase(); else return next(new Error('In _validateResponse: invalid B2C policy in id_token')); if (params.policy !== policy_in_idToken) return next(new Error("In _validateResponse: policy in id_token does not match the policy used")); } // check nonce if (!jwtClaims.nonce || jwtClaims.nonce === '' || jwtClaims.nonce !== optionsToValidate.nonce) return next(new Error('In _validateResponse: invalid nonce')); // check c_hash if (jwtClaims.c_hash) { // checkHashValueRS256 checks if code is null, so we don't bother here if (!aadutils.checkHashValueRS256(code, jwtClaims.c_hash)) return next(new Error("In _validateResponse: invalid c_hash")); } // check at_hash if (jwtClaims.at_hash) { // checkHashValueRS256 checks if access_token is null, so we don't bother here if (!aadutils.checkHashValueRS256(access_token, jwtClaims.at_hash)) return next(new Error("In _validateResponse: invalid at_hash")); } // return jwt claims and jwt claims string var idTokenSegments = id_token.split('.'); var jwtClaimsStr = base64url.decode(idTokenSegments[1]); return callback(jwtClaimsStr, jwtClaims); }); }
n/a
function authenticateStrategy(req, options) { /* * We should be careful using 'this'. Avoid the usage like `this.xxx = ...` * unless you know what you are doing. * * In the passport source code * (https://github.com/jaredhanson/passport/blob/master/lib/middleware/authenticate.js) * when it attempts to call the `oidcstrategy.authenticate` function, passport * creates an instance inherting oidcstrategy and then calls `instance.authenticate`. * Therefore, when we come here, `this` is the instance, its prototype is the * actual oidcstrategy, i.e. the `Strategy`. This means: * (1) `this._options = `, `this._verify = `, etc only adds new fields to the * instance, it doesn't change the values in oidcstrategy, i.e. `Strategy`. * (2) `this._options`, `this._verify`, etc returns the field in the instance, * and if there is none, returns the field in oidcstrategy, i.e. `strategy`. * (3) each time we call `authenticate`, we will get a brand new instance * * If you want to change the values in `Strategy`, use * const oidcstrategy = Object.getPrototypeOf(self); * to get the strategy first. * * Note: Simply do `const self = Object.getPrototypeOf(this)` and use `self` * won't work, since the `this` instance has a couple of functions like * success/fail/error... which `authenticate` will call. The following is the * structure of `this`: * * this * | -- success: function(user, info) * | -- fail: function(challenge, status) * | -- redirect: function(url, status) * | -- pass: function() * | -- __proto__: Strategy * | -- _verify * | -- _options * | -- ... * | -- __proto__: * | -- authenticate: function(req, options) * | -- ... */ const self = this; var resource = options && options.resourceURL; var customState = options && options.customState; var tenantIdOrName = options && options.tenantIdOrName; var login_hint = options && options.login_hint; var domain_hint = options && options.domain_hint; var prompt = options && options.prompt; var extraAuthReqQueryParams = options && options.extraAuthReqQueryParams; var extraTokenReqQueryParams = options && options.extraTokenReqQueryParams; var response = options && options.response; // validate tenantIdOrName if it is provided if (tenantIdOrName && !CONSTANTS.TENANTNAME_REGEX.test(tenantIdOrName) && !CONSTANTS.TENANTID_REGEX.test(tenantIdOrName)) return self.failWithLog(`In passport.authenticate: invalid tenantIdOrName ${tenantIdOrName}`); // 'params': items we get from the request or metadata, such as id_token, code, policy, metadata, cacheKey, etc var params = { 'tenantIdOrName': tenantIdOrName, 'extraAuthReqQueryParams': extraAuthReqQueryParams, 'extraTokenReqQueryParams ': extraTokenReqQueryParams }; // 'oauthConfig': items needed for oauth flow (like redirection, code redemption), such as token_endpoint, userinfo_endpoint, etc var oauthConfig = { 'resource': resource, 'customState': customState, 'domain_hint': domain_hint, 'login_hint': login_hint, 'prompt ': prompt, 'response': response }; // 'optionsToValidate': items we need to validate id_token against, such as issuer, audience, etc var optionsToValidate = {}; async.waterfall( [ /***************************************************************************** * Step 1. Collect information from the req and save the info into params ****************************************************************************/ (next) => { return self.collectInfoFromReq(params, req, next, response); }, /***************************************************************************** * Step 2. Load metadata, use the information from 'params' and 'self._options' * to configure 'oauthConfig' and 'options ...
...
To complete the sample, provide a route that corresponds to the path
configuration parameter that is sent to the strategy:
```javascript
app.get('/login',
passport.authenticate('azuread-openidconnect', { failureRedirect: '/
x27; }),
function(req, res) {
log.info('Login was called in the Sample');
res.redirect('/');
});
// POST /auth/openid/return
// Use passport.authenticate() as route middleware to authenticate the
...
collectInfoFromReq = function (params, req, next, response) { const self = this; // the things we will put into 'params': // err, err_description, id_token, code, policy, state, nonce, cachekey, metadata // ------------------------------------------------------------------------- // we shouldn't get any access_token or refresh_token from the request // ------------------------------------------------------------------------- if ((req.query && (req.query.access_token || req.query.refresh_token)) || (req.body && (req.body.access_token || req.body.refresh_token))) return next(new Error('In collectInfoFromReq: neither access token nor refresh token is expected in the incoming request')); // ------------------------------------------------------------------------- // we might get err, id_token, code, state from the request // ------------------------------------------------------------------------- var source = null; if (req.query && (req.query.error || req.query.id_token || req.query.code)) source = req.query; else if (req.body && (req.body.error || req.body.id_token || req.body.code)) source = req.body; if (source) { params.err = source.error; params.err_description = source.error_description; params.id_token = source.id_token; params.code = source.code; params.state = source.state; if (source.state && source.state.length >= 38) { // the random generated state always has 32 characters. This state is longer than 32 // so it must be a custom state. We added 'CUSTOM' prefix and a random 32 byte long // string in front of the original custom state, now we change it back. if (!source.state.startsWith('CUSTOM')) return next(new Error(`In collectInfoFromReq: invalid custom state ${state}`)); source.state = source.state.substring(38); } } // ------------------------------------------------------------------------- // If we received code, id_token or err, we must have received state, now we // find the state/nonce/policy tuple from session. // If we received none of them, find policy in query // ------------------------------------------------------------------------- if (params.id_token || params.code || params.err) { if (!params.state) return next(new Error('In collectInfoFromReq: missing state in the request')); var tuple; if (!self._useCookieInsteadOfSession) tuple = self._sessionContentHandler.findAndDeleteTupleByState(req, self._key, params.state); else tuple = self._cookieContentHandler.findAndDeleteTupleByState(req, response, params.state); if (!tuple) return next(new Error('In collectInfoFromReq: invalid state received in the request')); params.nonce = tuple['nonce']; params.policy = tuple['policy']; params.resource = tuple['resource']; // user provided tenantIdOrName will be ignored for redirectUrl, since we saved the one we used in session if (params.tenantIdOrName) log.info(`user provided tenantIdOrName '${params.tenantIdOrName}' is ignored for redirectUrl, we will use the one stored in session`); params.tenantIdOrName = tuple['tenantIdOrName']; } else { params.policy = req.query.p ? req.query.p.toLowerCase() : null; } // if we are not using the common endpoint, but we have tenantIdOrName, just ignore it if (!self._options.isCommonEndpoint && params.tenantIdOrName) { log.info(`identityMetadata is tenant-specific, so we ignore the tenantIdOrName '${params.tenantIdOrName}'`); params.tenantIdOrName = null; } // if we are using the common endpoint and we want to validate issuer, then user has to // provide issuer in config, or provide tenant id or name using tenantIdOrName option in // passport.authenticate. Otherwise we won't know the issuer. if (self._options.isCommonEndpoint && self._options.validateIssuer && (!self._options.issuer && !params.tenantIdOrName)) return next(new Error('In collectInfoFromReq: issu ...
n/a
failWithLog = function (message) { this.log.info(`authentication failed due to: ${message}`); return this.fail(message); }
n/a
function setOptions(params, oauthConfig, optionsToValidate, done) { const self = this; async.waterfall([ // ------------------------------------------------------------------------ // load metadata // ------------------------------------------------------------------------ (next) => { memoryCache.wrap(params.cachekey, (cacheCallback) => { params.metadata.fetch((fetchMetadataError) => { if (fetchMetadataError) { return cacheCallback(new Error(`In setOptions: Unable to fetch metadata`)); } return cacheCallback(null, params.metadata); }); }, { ttl }, next); }, // ------------------------------------------------------------------------ // set oauthConfig: the information we need for oauth flow like redeeming code/access_token // ------------------------------------------------------------------------ (metadata, next) => { if (!metadata.oidc) return next(new Error('In setOptions: failed to load metadata')); params.metadata = metadata; // copy the fields needed into 'oauthConfig' aadutils.copyObjectFields(metadata.oidc, oauthConfig, ['authorization_endpoint', 'token_endpoint', 'userinfo_endpoint']); aadutils.copyObjectFields(self._options, oauthConfig, ['clientID', 'clientSecret', 'privatePEMKey', 'thumbprint', 'responseType ', 'responseMode', 'scope', 'redirectUrl']); oauthConfig.tenantIdOrName = params.tenantIdOrName; oauthConfig.extraAuthReqQueryParams = params.extraAuthReqQueryParams; oauthConfig.extraTokenReqQueryParams = params.extraTokenReqQueryParams; // validate oauthConfig const validatorConfig = { authorization_endpoint: Validator.isHttpsURL, token_endpoint: Validator.isHttpsURL, userinfo_endpoint: Validator.isHttpsURLIfExists, }; try { // validator will throw exception if a required option is missing const checker = new Validator(validatorConfig); checker.validate(oauthConfig); } catch (ex) { return next(new Error(`In setOptions: ${aadutils.getErrorMessage(ex)}`)); } // for B2C, verify the endpoints in oauthConfig has the correct policy if (self._options.isB2C){ var policyChecker = (endpoint, policy) => { var u = {}; try { u = url.parse(endpoint, true); } catch (ex) { } return u.query && u.query.p && (policy.toLowerCase() === u.query.p.toLowerCase()); }; // B2C has no userinfo_endpoint, so no need to check it if (!policyChecker(oauthConfig.authorization_endpoint, params.policy)) return next(new Error(`policy in ${oauthConfig.authorization_endpoint} should be ${params.policy}`)); if (!policyChecker(oauthConfig.token_endpoint, params.policy)) return next(new Error(`policy in ${oauthConfig.token_endpoint} should be ${params.policy}`)); } return next(null, metadata); }, // ------------------------------------------------------------------------ // set optionsToValidate: the information we need for id_token validation. // we do this only if params has id_token or code, otherwise there is no // id_token to validate // ------------------------------------------------------------------------ (metadata, next) => { if (!params.id_token && !params.code) return next(null); // set items from self._options aadutils.copyObjectFields(self._options, optionsToValidate, ['validateIssuer', 'audience', 'allowMultiAudiencesInToken', 'ignoreExpiration', 'allowMultiAudiencesInToken']); // algorithms var algorithms = metadata.oidc.algorithms; if (!algorithms) return next(new Error('In setOptions: algorithms is missing in metadata')); if (!Array.isArray(algorithms) || algorithms.length == 0 || (algorithms.length === 1 && algorithms[0] === 'no ...
n/a