UNPKG

passport-google-idtoken

Version:

Passport Strategy that uses a Google id_token and validates against Google's tokeninfo endpoint.

163 lines (150 loc) 5.8 kB
var Strategy = require('passport').Strategy; var request = require('request'); var util = require('util'); var _ = require('underscore'); /** * A Passport Strategy for authenticating a User with Google from a Google * supplied id token from their client library. * * @param options {object} should contain the following properties: * tokenParamName {string, optional} the query parameter name to get the id token value from. default is 'id_token'. * tokenInfoUrl {string, optional} the url to get token info from Google. default is 'https://www.googleapis.com/oauth2/v3/tokeninfo'. * @param verify {function} the callback function that authenticates a User. * Parameters to verify are as follows: * profile {object} with properties: * iss {string} - issuer of token. should be 'https://accounts.google.com' * sub {string} - unique Google ID of the user. * azp {string} - application's client id. * aud {string} - application's client id. * iat {Date} - time at which the token was issued. * exp {Date} - time at which the token will expire. * hd {string} - the hosted domain of the user. only present if user belongs to Google Apps for Work hosted domain. * * The following fields are only included when the user has granted the 'profile' and 'email' * OAuth scoped to the application. * email {string} - the Google user's email. * email_verified {string} - 'true' or 'false'. * name {string} * picture {string} - url. * given_name {string} * family_name {string} * locale {string} */ var GoogleIdTokenStrategy = function(options, verify) { Strategy.call(this); //this is the name that will need to be used in client code. this.name = 'google-idtoken'; options = _.extend({}, options); this.tokenParamName = options.tokenParamName || 'id_token'; this.tokenInfoUrl = options.tokenInfoUrl || 'https://www.googleapis.com/oauth2/v3/tokeninfo'; this.verify = verify; } util.inherits(GoogleIdTokenStrategy, Strategy); /** * Authentication entrypoint called from Passport. * @inheritDoc. */ GoogleIdTokenStrategy.prototype.authenticate = function(req, options) { var idToken = this.paramFromRequest(req, this.tokenParamName); if (!idToken) { return this.done('parameter ' + this.tokenParamName + ' is not present in req'); } this.validateIdToken(idToken, _.bind(function(error, profile) { if (error) { return this.done(error); } return this.verify(profile, _.bind(this.done, this)); }, this)); } /** * Callback for when a user has been found from verify client code. * @param error {mixed} the error while verifying the user. * @param user {mixed} the user. * @param info {object}. */ GoogleIdTokenStrategy.prototype.done = function(error, user, info) { if (error) { return this.error(error); } else if (!user) { return this.fail(info); } this.success(user); } /** * Gets the id token value from req using name for lookup in req.body, req.query, * and req.params. * @param req {Express Request} * @parma name {string} the key to use to lookup id token in req. */ GoogleIdTokenStrategy.prototype.paramFromRequest = function(req, name) { var body = req.body || {}; var query = req.query || {}; var params = req.params || {}; if (body[name]) { return body[name]; } if (query[name]) { return query[name]; } return params[name] || ''; } /** * Validates that idToken is valid by getting token info from the token info endpoint from Google. * @param idToken {string} the id token from a Google client. * @param callback {function} profile object callback. * @param poster {function, optional for testing}. */ GoogleIdTokenStrategy.prototype.validateIdToken = function(idToken, callback, poster) { poster = poster || request; poster.post({ url: this.tokenInfoUrl, form: { id_token: idToken } }, (error, response, body) => { if (error) { return callback(error); } var profile = {}; try { profile = this.makeProfile(JSON.parse(body)); } catch (e) { return callback('Could not parse response: ' + body); } if (!(profile.exp && profile.email)) { return callback('profile.exp and profile.email must be defined in token info response'); } if ((new Date()).getTime() > profile.exp) { return callback('Token is expired'); } callback(null, profile); }); } /** * Creates a profile object (to send to this.verify) from Google's token info response. * @param response {object} JSON parsed response body from Google token info endpoint. * @return {object} with email and exp fields. */ GoogleIdTokenStrategy.prototype.makeProfile = function(response) { var result = { iss: response.iss, sub: response.sub, azp: response.azp, aud: response.aud, iat: response.iat, exp: response.exp, hd: response.hd, email: response.email, email_verified: response.email_verified, name: response.name, picture: response.picture, given_name: response.given_name, family_name: response.family_name, locale: response.locale }; //multiply by 1000 here because Google returns number of seconds and we need millseconds for Date. result.iat = !!result.iat ? new Date(1000 * parseInt(result.iat)) : undefined; result.exp = !!result.exp ? new Date(1000 * parseInt(result.exp)) : undefined; return result; } module.exports = GoogleIdTokenStrategy;