First attempt was done here
Second attempt was done here
Huge comment at top
/*
* Export single function that creates the passportControl object
* The function has two parameters:
* app: The nodejs/express service object.
* This is used to register the end points
* this authentication object listens too.
* register: An object that abstracts user registrations it
* must support the following methods:
* updateUser({<user object>})
* getSavedUser(localUserId, function(err, localUser) {})
*
* It also uses an external module for configuration (ie holding all the secrets)
* This file is not in source control (but in a key repository nice and safe).
* config: Expected fields:
* config.app The URL of the site.
* config.passport An object containing the secrets for each service
* The values will depend on the service and the
* implementation of passport-<service> module
* See the passport code for more detail
* Example:
*
{
app: 'iPubCrawlMaps.com',
short: 'iPCM',
passport: {
facebook: {
clientID: '<My FaceBook Client>'
clientSecret: '<My FaceBool Secret>'
},
twitter: {
consumerKey: '<My Twitter Key>',
consumerSecret: '<My Twitter Secret>'
},
google: {
returnURL: 'http://<My Site>/api/auth/callback?type=google',
realm: 'http://<My Site>/'
},
foursquare: {
clientID: '<My FourSquare ID>',
clientSecret: '<My FourSquare Secret>'
},
linkedin: {
consumerKey: '<My LinkedIn Key>',
consumerSecret: '<My LinkedIn Secret>'
},
github: {
clientID: '<My GitHub ID>',
clientSecret: '<My GitHub Secret>'
},
meetup: {
consumerKey: '<My Meetup Key>',
consumerSecret: '<My Meetup Secret>'
},
aol: {
returnURL: 'http://<My Site>/api/auth/callback?type=aol',
realm: 'http://<My Site>/'
},
yahoo: {
returnURL: 'http://<My Site>/api/auth/callback?type=yahoo',
realm: 'http://<My Site>/'
vimeo: {
consumerKey: '<My Vimeo Key>',
consumerSecret: '<My Vimeo Secret>'
},
instagram: {
clientID: '<My Instagram ID>',
clientSecret: '<My Instagram Secret>'
},
tumblr: {
consumerKey: '<My Tumbler Key>',
consumerSecret: '<My Tumbler Secret>'
}
}
};
*
* The passport control object is supposed to be a wrapper for
* nodejs/express/passport authentication.
*
* When the object is created it adds three end points to the server for authentication
* /api/auth?type=<AuthenticationType>
* /api/auth/callback?type=<AuthenticationType>
* /api/authexit
*
* Where AuthenticationType is the service doing the authentication.
* Eg Facebook/Twitter/Amazon etc
*
* A fourth end point is added to get display info about the user:
* And information about supported authentication services. This allows the
* front-end to display the appropriate controls without needing code changes.
* /api/userInfo
*
* This end point returns the following json object:
* {
* logedin: true if the user is currently logged in; false otherwise.
* displayName: The users display name if logged in, '' otherwise
* loginMethods: A list of services that can be used to login if not logged in.
* }
*
* This object has two public methods:
* checkPassport(req, res)
* registerUser(req, res)
*
* req: http request received from node.
* res: response object we use to reply to the request.
*
* These are automatically hooked up to the exposed endpoints.
* To extend this for any particular service just add the appropriate
* objects to the array built with buildData()
*
*/
Global Config I use
// Global object for correctly escaping URL
var querystring = require('querystring');
var config = require('../config.js');
The call to Register a user
function registerUser(register, provider, providerId, displayName, done) {
register.updateUser({
provider: provider,
providerId: providerId,
displayName: displayName
},
function(err, localUser) {
if (err) {done(err); return; }
done(null, localUser);
}
);
}
The heart. A standard function for registering a login stratergy
function addStandardStratergy(stratergyConfig, name, prittyName, type, typeName, config, loginAction) {
config.callbackURL = 'http://' + stratergyConfig.app + '/api/auth/callback?type=' + name;
var Strategy = require(type).Strategy;
stratergyConfig.passport.use(new Strategy(config, loginAction));
stratergyConfig.result.auth[name] = stratergyConfig.passport.authenticate(name);
stratergyConfig.result.callback[name] = function(req, res, page) {stratergyConfig.passport.authenticate(name, { successRedirect: page, failureRedirect: '/login'})(req, res); };
stratergyConfig.result.services.push({type: name, classInfo: typeName, display: prittyName});
}
Build the data. Basically calls addStandardStratergy() with custom param for each site.
/*
* This builds the data object central to 'passportControl'
* The Key: Is the name of the 'AuthenticationType' the value is the passport object that does the authentication.
* auth: Handles the initial authentication request.
* callback: Handles the callback from the authentication service
* services: A list of social services that can be used for logging in
*/
function buildData(passport, register) {
// Add more strategies as required here.
var result, stratergyConfig, useDisplayName, useUserName, buildName, OAuthGetLoginAction, OpenIdLoginAction;
result = {
auth: {
default: function(req, res) {res.redirect('/login?' + querystring.stringify({reg_error: 'Invalid Authentication Type (attempt)'})); }
},
callback: {
default: function(req, res) {res.redirect('/login?' + querystring.stringify({reg_error: 'Invalid Authentication Type (callback)'})); }
},
services: []
};
/*
* Add a call for each social network you want to use for registration
*/
stratergyConfig = {
passport: passport,
result: result,
app: config.app
};
/*
* I only use these services to log people in to associate them to an account.
* Each of the services I use provides varying information. All I want is a display name.
* But each service provides a different set of attributes. So I use the following functions
* in association with addStandardStratergy() to pull the "Display Name" from the returned data.
*/
useDisplayName = function(profile) {return profile.displayName || 'anonymous'; };
useUserName = function(profile) {return profile.username || 'ananymous'; };
buildName = function(profile) {return profile.name.givenName + ' ' + profile.name.familyName; };
/*
* Two major authentication methods are used by these sites:
* OAuth
* OpenID
*
* A method for handling each type of authentication.
*/
OAuthGetLoginAction = function (name, getDisplayName) {
return function(accessToken, refreshToken, profile, done) {
registerUser(register, profile.provider, profile.id, getDisplayName(profile), done);
};
};
OpenIdLoginAction = function OpenIdLoginAction(name) {
return function(identifier, profile, done) {
registerUser(register, name, identifier, useDisplayName(profile), done);
};
};
/*
* Register the different services you want to use here.
* The authentication information is the 5th paramaeter and pulled from the config info
* described in the main comment above
*/
// name prittyName type typeName config loginAction
// Name: Used internally and for callbacks.
// prittyName: Displayed to the user in any API
// typeName: Used for CSS typing in the display.
// config: Object that holds config information for passport.
// loginAction: Function called on successful login to retrieve specific user info (I just get Display name).
addStandardStratergy(stratergyConfig, 'facebook', 'Facebook', 'passport-facebook', 'facebook', config.passport.facebook, OAuthGetLoginAction('facebook', useDisplayName));
addStandardStratergy(stratergyConfig, 'twitter', 'Twitter', 'passport-twitter', 'twitter', config.passport.twitter, OAuthGetLoginAction('twitter', useDisplayName));
addStandardStratergy(stratergyConfig, 'google', 'Google+', 'passport-google', 'google-plus', config.passport.google, OpenIdLoginAction('google'));
addStandardStratergy(stratergyConfig, 'foursquare', 'Foursquare','passport-foursquare', 'foursquare', config.passport.foursquare, OAuthGetLoginAction('foursquare', buildName));
addStandardStratergy(stratergyConfig, 'linkedin', 'Linkedin', 'passport-linkedin', 'linkedin', config.passport.linkedin, OAuthGetLoginAction('linkedin', useDisplayName));
addStandardStratergy(stratergyConfig, 'github', 'GitHub', 'passport-github', 'github', config.passport.github, OAuthGetLoginAction('github', useDisplayName));
addStandardStratergy(stratergyConfig, 'meetup', 'Meetup', 'passport-meetup', 'meetup', config.passport.meetup, OAuthGetLoginAction('meetup', useDisplayName));
addStandardStratergy(stratergyConfig, 'aol', 'AOL', 'passport-aol', 'aol', config.passport.aol, OpenIdLoginAction('aol'));
addStandardStratergy(stratergyConfig, 'yahoo', 'Yahoo!', 'passport-yahoo', 'yahoo', config.passport.yahoo, OpenIdLoginAction('yahoo'));
addStandardStratergy(stratergyConfig, 'vimeo', 'Vimeo', 'passport-vimeo', 'vimeo', config.passport.vimeo, OAuthGetLoginAction('vimeo', useDisplayName));
addStandardStratergy(stratergyConfig, 'instagram', 'Instagram','passport-instagram', 'instagram', config.passport.instagram, OAuthGetLoginAction('instagram', useDisplayName));
addStandardStratergy(stratergyConfig, 'tumblr', 'Tumblr', 'passport-tumblr', 'tumblr', config.passport.tumblr, OAuthGetLoginAction('tumblr', useUserName));
return result;
}
The object we export back to the applicaiton.
See (second try for usage).
module.exports = function(app, register) {
// App: Application object
// register: The user registration service
// This has been abstracted from the passport authentication code.
// I will document this interface separately.
// Get the passport object we reap
// Correctly initialize and turn on sessions.
var passport, passportControl;
passport = require('passport');
app.use(passport.initialize());
app.use(passport.session());
// Set passport to only save the user ID to the session
passport.serializeUser(function(localUser, done) {
done(null, localUser.id);
});
// Set passport to retrieve the user object using the
// saved id (see serializeUser).
passport.deserializeUser(function(localUserId, done) {
register.getSavedUser(localUserId, function(err, localUser) {
if (err) { done(err); return; }
done(null, localUser);
});
});
// Create the passport control object
passportControl = {
data: buildData(passport, register),
checkPassport: function(req, res) {
req.session.page = req.query.page || '/';
return this.performAction(this.data.auth, req, res);
},
registerUser: function(req, res) {
req.query.page = req.session.page;
return this.performAction(this.data.callback, req, res);
},
deAuthorize: function(req, res) {
var page = req.query.page || '/';
req.logout();
res.redirect(page);
},
performAction: function (dataItem, req, res) {
var action, page;
action = dataItem[req.query.type];
page = req.query.page || '/';
if (action === null) {
action = dataItem['default'];
}
return action(req, res, page);
},
authTypes: function() {
return this.data.services;
}
};
// The service endpoints
// This will control all authentication.
app.get('/api/authexit', function(req, res) { passportControl.deAuthorize(req, res); });
app.get('/api/auth', function(req, res) { passportControl.checkPassport(req, res); });
app.get('/api/auth/callback', function(req, res) { passportControl.registerUser(req, res); });
app.get('/api/userInfo', function(req, res) {
res.json({
logedin: req.user ? true : false,
displayName: req.user ? req.user.displayName : '',
loginMethods: req.user ? [] : passportControl.authTypes()
});
});
return passportControl;
};