passport-google-oauth2
Version:
Passport strategy for Google OAuth 2.0
226 lines (208 loc) • 6.94 kB
JavaScript
/**
* Module dependencies.
*/
var util = require('util')
, OAuth2Strategy = require('passport-oauth2')
, InternalOAuthError = require('passport-oauth2').InternalOAuthError;
/**
* `Strategy` constructor.
*
* The Google authentication strategy authenticates requests by delegating to
* Google using the OAuth 2.0 protocol.
*
* Applications must supply a `verify` callback which accepts an `accessToken`,
* `refreshToken` and service-specific `profile`, and then calls the `done`
* callback supplying a `user`, which should be set to `false` if the
* credentials are not valid. If an exception occured, `err` should be set.
*
* Options:
* - `clientID` your Google application's client id
* - `clientSecret` your Google application's client secret
* - `callbackURL` URL to which Google will redirect the user after granting authorization
*
* Examples:
*
* passport.use(new GoogleStrategy({
* clientID: '123-456-789',
* clientSecret: 'shhh-its-a-secret'
* callbackURL: 'https://www.example.net/auth/google/callback'
* },
* function(accessToken, refreshToken, profile, done) {
* User.findOrCreate(..., function (err, user) {
* done(err, user);
* });
* }
* ));
*
* @param {Object} options
* @param {Function} verify
* @api public
*/
function Strategy(options, verify) {
options = options || {};
options.authorizationURL = options.authorizationURL || 'https://accounts.google.com/o/oauth2/v2/auth';
options.tokenURL = options.tokenURL || 'https://www.googleapis.com/oauth2/v4/token';
OAuth2Strategy.call(this, options, verify);
this.name = 'google';
}
/**
* Inherit from `OAuth2Strategy`.
*/
util.inherits(Strategy, OAuth2Strategy);
Strategy.prototype.authenticate = function(req, options) {
options || (options = {})
var oldHint = options.loginHint
options.loginHint = req.query.login_hint
OAuth2Strategy.prototype.authenticate.call(this, req, options)
options.loginHint = oldHint
}
/**
* Retrieve user profile from Google.
*
* This function constructs a normalized profile, with the following properties:
*
* - `provider` always set to `google`
* - `id`
* - `name`
* - `displayName`
* - `birthday`
* - `relationship`
* - `isPerson`
* - `isPlusUser`
* - `placesLived`
* - `language`
* - `emails`
* - `gender`
* - `picture`
*
* @param {String} accessToken
* @param {Function} done
* @api protected
*/
Strategy.prototype.userProfile = function(accessToken, done) {
this._oauth2.get('https://www.googleapis.com/oauth2/v3/userinfo', accessToken, function (err, body, res) {
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }
try {
var json = JSON.parse(body);
var profile = { provider: 'google' };
profile.sub = json.sub;
profile.id = json.id || json.sub;
profile.displayName = json.name;
profile.name = {
givenName: json.given_name,
familyName: json.family_name
};
profile.given_name = json.given_name;
profile.family_name = json.family_name;
if (json.birthday) profile.birthday = json.birthday;
if (json.relationshipStatus) profile.relationship = json.relationshipStatus;
if (json.objectType && json.objectType == 'person') {
profile.isPerson = true;
}
if (json.isPlusUser) profile.isPlusUser = json.isPlusUser;
if (json.email_verified !== undefined) {
profile.email_verified = json.email_verified;
profile.verified = json.email_verified;
}
if (json.placesLived) profile.placesLived = json.placesLived;
if (json.language) profile.language = json.language;
if (!json.language && json.locale) {
profile.language = json.locale;
profile.locale = json.local;
}
if (json.emails) {
profile.emails = json.emails;
profile.emails.some(function(email) {
if (email.type === 'account') {
profile.email = email.value
return true
}
})
}
if (!profile.email && json.email) {
profile.email = json.email;
}
if (!profile.emails && profile.email) {
profile.emails = [{
value: profile.email,
type: "account"
}];
}
if (json.gender) profile.gender = json.gender;
if (!json.domain && json.hd) json.domain = json.hd;
if (json.image && json.image.url) {
var photo = {
value: json.image.url
};
if (json.image.isDefault) photo.type = 'default';
profile.photos = [photo];
}
if (!json.image && json.picture) {
var photo = {
value: json.picture
};
photo.type = 'default';
profile.photos = [photo];
profile.picture = json.picture;
}
if (json.cover && json.cover.coverPhoto && json.cover.coverPhoto.url)
profile.coverPhoto = json.cover.coverPhoto.url;
profile._raw = body;
profile._json = json;
done(null, profile);
} catch(e) {
done(e);
}
});
};
/**
* Return extra Google-specific parameters to be included in the authorization
* request.
*
* @param {Object} options
* @return {Object}
* @api protected
*/
Strategy.prototype.authorizationParams = function(options) {
var params = {};
if (options.accessType) {
params['access_type'] = options.accessType;
}
if (options.approvalPrompt) {
params['approval_prompt'] = options.approvalPrompt;
}
if (options.prompt) {
// This parameter is undocumented in Google's official documentation.
// However, it was detailed by Breno de Medeiros (who works at Google) in
// this Stack Overflow answer:
// http://stackoverflow.com/questions/14384354/force-google-account-chooser/14393492#14393492
params['prompt'] = options.prompt;
}
if (options.loginHint) {
// This parameter is derived from OpenID Connect, and supported by Google's
// OAuth 2.0 endpoint.
// https://github.com/jaredhanson/passport-google-oauth/pull/8
// https://bitbucket.org/openid/connect/commits/970a95b83add
params['login_hint'] = options.loginHint;
}
if (options.userID) {
// Undocumented, but supported by Google's OAuth 2.0 endpoint. Appears to
// be equivalent to `login_hint`.
params['user_id'] = options.userID;
}
if (options.hostedDomain || options.hd) {
// This parameter is derived from Google's OAuth 1.0 endpoint, and (although
// undocumented) is supported by Google's OAuth 2.0 endpoint was well.
// https://developers.google.com/accounts/docs/OAuth_ref
params['hd'] = options.hostedDomain || options.hd;
}
return params;
};
/**
* Expose `Strategy` directly from package.
*/
exports = module.exports = Strategy;
/**
* Export constructors.
*/
exports.Strategy = Strategy;