passport-oauth-no-web-server
Version:
OAuth 1.0 and 2.0 authentication strategies for Passport.
247 lines (221 loc) • 8.98 kB
JavaScript
/**
* Module dependencies.
*/
var passport = require('passport')
, url = require('url')
, util = require('util')
, utils = require('./utils')
, OAuth2 = require('oauth-no-web-server').OAuth2
, InternalOAuthError = require('../errors/internaloautherror');
/**
* `OAuth2Strategy` constructor.
*
* The OAuth 2.0 authentication strategy authenticates requests using the OAuth
* 2.0 protocol.
*
* OAuth 2.0 provides a facility for delegated authentication, whereby users can
* authenticate using a third-party service such as Facebook. Delegating in
* this manner involves a sequence of events, including redirecting the user to
* the third-party service for authorization. Once authorization has been
* granted, the user is redirected back to the application and an authorization
* code can be used to obtain credentials.
*
* 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:
* - `authorizationURL` URL used to obtain an authorization grant
* - `tokenURL` URL used to obtain an access token
* - `clientID` identifies client to service provider
* - `clientSecret` secret used to establish ownership of the client identifer
* - `callbackURL` URL to which the service provider will redirect the user after obtaining authorization
* - `passReqToCallback` when `true`, `req` is the first argument to the verify callback (default: `false`)
*
* Examples:
*
* passport.use(new OAuth2Strategy({
* authorizationURL: 'https://www.example.com/oauth2/authorize',
* tokenURL: 'https://www.example.com/oauth2/token',
* clientID: '123-456-789',
* clientSecret: 'shhh-its-a-secret'
* callbackURL: 'https://www.example.net/auth/example/callback'
* },
* function(accessToken, refreshToken, profile, done) {
* User.findOrCreate(..., function (err, user) {
* done(err, user);
* });
* }
* ));
*
* @param {Object} options
* @param {Function} verify
* @api public
*/
function OAuth2Strategy(options, verify) {
options = options || {}
passport.Strategy.call(this);
this.name = 'oauth2';
this._verify = verify;
if (!options.authorizationURL) throw new Error('OAuth2Strategy requires a authorizationURL option');
if (!options.tokenURL) throw new Error('OAuthStrategy requires a tokenURL option');
if (!options.clientID) throw new Error('OAuth2Strategy requires a clientID option');
if (!options.clientSecret) throw new Error('OAuth2Strategy requires a clientSecret option');
// NOTE: The _oauth2 property is considered "protected". Subclasses are
// allowed to use it when making protected resource requests to retrieve
// the user profile.
this._oauth2 = new OAuth2(options.clientID, options.clientSecret,
'', options.authorizationURL, options.tokenURL, options.customHeaders);
this._callbackURL = options.callbackURL;
this._scope = options.scope;
this._scopeSeparator = options.scopeSeparator || ' ';
this._passReqToCallback = options.passReqToCallback;
this._skipUserProfile = (options.skipUserProfile === undefined) ? false : options.skipUserProfile;
}
/**
* Inherit from `passport.Strategy`.
*/
util.inherits(OAuth2Strategy, passport.Strategy);
/**
* Authenticate request by delegating to a service provider using OAuth 2.0.
*
* @param {Object} req
* @api protected
*/
OAuth2Strategy.prototype.authenticate = function(req, options) {
options = options || {};
var self = this;
if (req.query && req.query.error) {
// TODO: Error information pertaining to OAuth 2.0 flows is encoded in the
// query parameters, and should be propagated to the application.
return this.fail();
}
var callbackURL = options.callbackURL || this._callbackURL;
if (callbackURL) {
var parsed = url.parse(callbackURL);
if (!parsed.protocol) {
// The callback URL is relative, resolve a fully qualified URL from the
// URL of the originating request.
callbackURL = url.resolve(utils.originalURL(req), callbackURL);
}
}
if (req.query && req.query.code) {
var code = req.query.code;
// NOTE: The module oauth (0.9.5), which is a dependency, automatically adds
// a 'type=web_server' parameter to the percent-encoded data sent in
// the body of the access token request. This appears to be an
// artifact from an earlier draft of OAuth 2.0 (draft 22, as of the
// time of this writing). This parameter is not necessary, but its
// presence does not appear to cause any issues.
this._oauth2.getOAuthAccessToken(code, { grant_type: 'authorization_code', redirect_uri: callbackURL },
function(err, accessToken, refreshToken, params) {
if (err) { return self.error(new InternalOAuthError('failed to obtain access token', err)); }
self._loadUserProfile(accessToken, function(err, profile) {
if (err) { return self.error(err); };
function verified(err, user, info) {
if (err) { return self.error(err); }
if (!user) { return self.fail(info); }
self.success(user, info);
}
if (self._passReqToCallback) {
var arity = self._verify.length;
if (arity == 6) {
self._verify(req, accessToken, refreshToken, params, profile, verified);
} else { // arity == 5
self._verify(req, accessToken, refreshToken, profile, verified);
}
} else {
var arity = self._verify.length;
if (arity == 5) {
self._verify(accessToken, refreshToken, params, profile, verified);
} else { // arity == 4
self._verify(accessToken, refreshToken, profile, verified);
}
}
});
}
);
} else {
// NOTE: The module oauth (0.9.5), which is a dependency, automatically adds
// a 'type=web_server' parameter to the query portion of the URL.
// This appears to be an artifact from an earlier draft of OAuth 2.0
// (draft 22, as of the time of this writing). This parameter is not
// necessary, but its presence does not appear to cause any issues.
var params = this.authorizationParams(options);
params['response_type'] = 'code';
params['redirect_uri'] = callbackURL;
var scope = options.scope || this._scope;
if (scope) {
if (Array.isArray(scope)) { scope = scope.join(this._scopeSeparator); }
params.scope = scope;
}
var state = options.state;
if (state) { params.state = state; }
var location = this._oauth2.getAuthorizeUrl(params);
this.redirect(location);
}
}
/**
* Retrieve user profile from service provider.
*
* OAuth 2.0-based authentication strategies can overrride this function in
* order to load the user's profile from the service provider. This assists
* applications (and users of those applications) in the initial registration
* process by automatically submitting required information.
*
* @param {String} accessToken
* @param {Function} done
* @api protected
*/
OAuth2Strategy.prototype.userProfile = function(accessToken, done) {
return done(null, {});
}
/**
* Return extra parameters to be included in the authorization request.
*
* Some OAuth 2.0 providers allow additional, non-standard parameters to be
* included when requesting authorization. Since these parameters are not
* standardized by the OAuth 2.0 specification, OAuth 2.0-based authentication
* strategies can overrride this function in order to populate these parameters
* as required by the provider.
*
* @param {Object} options
* @return {Object}
* @api protected
*/
OAuth2Strategy.prototype.authorizationParams = function(options) {
return {};
}
/**
* Load user profile, contingent upon options.
*
* @param {String} accessToken
* @param {Function} done
* @api private
*/
OAuth2Strategy.prototype._loadUserProfile = function(accessToken, done) {
var self = this;
function loadIt() {
return self.userProfile(accessToken, done);
}
function skipIt() {
return done(null);
}
if (typeof this._skipUserProfile == 'function' && this._skipUserProfile.length > 1) {
// async
this._skipUserProfile(accessToken, function(err, skip) {
if (err) { return done(err); }
if (!skip) { return loadIt(); }
return skipIt();
});
} else {
var skip = (typeof this._skipUserProfile == 'function') ? this._skipUserProfile() : this._skipUserProfile;
if (!skip) { return loadIt(); }
return skipIt();
}
}
/**
* Expose `OAuth2Strategy`.
*/
module.exports = OAuth2Strategy;