node.js authentication using passport facebook strategy

Passport is a node module that is used as an authentication middleware. The only purpose of this module is to authenticate requests. It abstracts away the complexity of authentication process, which makes the application code more clean and maintainable. A strategy is a separate module that needs to be installed alone with the passport module. For example, if you are storing the username and password of a user in your own database, then a the module passport-local need to be installed together with passport. If you are using facebook, twitter, google’s api to authenticate users, then passort-facebook, passport-twitter and passport-goolge will have to be installed. Other than facebook, twitter, and google, passport supports hundreds of other api for authentication.

The code below demonstrates how to use passport with express framework to do authentication through facebook login, aka authenticate the user using facebook.
passport_facebook.js

var express  			= require('express'),
	app      			= express(),
	passport 			= require('passport'),
	FacebookStrategy	= require('passport-facebook').Strategy,
	session      		= require('express-session');

var facebookAuth = {
        'clientID'        : '313227515533228', // facebook App ID
        'clientSecret'    : 'e5e96c56b76d4254ebc5878d57c6e8da', // facebook App Secret
        'callbackURL'     : 'http://localhost:8080/auth/facebook/callback'
    };

// hardcoded users, ideally the users should be stored in a database
var users = [
{"id":111, "username":"amy", "password":"amyspassword"},
{ 
	"id" : "222",
	"email" : "test@test.com", 
	"name" : "Ben", 
	"token" : "DeSag3sEgaEGaYRNKlQp05@diorw"}
];
function findUser(id) {
	for(var i=0; i<users.length; i++) {
		if(id === users[i].id) {
			return users[i]
		}
	}
	return null;
}

// passport needs ability to serialize and unserialize users out of session
passport.serializeUser(function (user, done) {
    done(null, users[0].id);
});
passport.deserializeUser(function (id, done) {
	done(null, users[0]);
});
 
// passport facebook strategy
passport.use(new FacebookStrategy({
    "clientID"        : facebookAuth.clientID,
    "clientSecret"    : facebookAuth.clientSecret,
    "callbackURL"     : facebookAuth.callbackURL
},
function (token, refreshToken, profile, done) {
	var user = findUser(profile.id);
	if (user) {
		console.log(users);
		return done(null, user);
	} else {
		var newUser = {
			"id": 		profile.id,
			"name": 	profile.name.givenName + ' ' + profile.name.familyName,
			"email": 	(profile.emails[0].value || '').toLowerCase(),
			"token": 	token
		};
		users.push(newUser);
		console.log(users);
		return done(null, newUser);
	}
}));


// initialize passposrt and and session for persistent login sessions
app.use(session({
	secret: "tHiSiSasEcRetStr",
	resave: true,
    saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());


// route middleware to ensure user is logged in, if it's not send 401 status
function isLoggedIn(req, res, next) {
    if (req.isAuthenticated())
        return next();

    res.sendStatus(401);
}

// home page
app.get("/", function (req, res) {
	res.send("Hello!");
});

// login page
app.get("/login", function (req, res) {
	res.send("<a href='/auth/facebook'>login through facebook</a>");
});


// send to facebook to do the authentication
app.get("/auth/facebook", passport.authenticate("facebook", { scope : "email" }));
// handle the callback after facebook has authenticated the user
app.get("/auth/facebook/callback",
    passport.authenticate("facebook", {
        successRedirect : "/content",
        failureRedirect : "/"
}));


// content page, it calls the isLoggedIn function defined above first
// if the user is logged in, then proceed to the request handler function,
// else the isLoggedIn will send 401 status instead
app.get("/content", isLoggedIn, function (req, res) {
	res.send("Congratulations! you've successfully logged in.");
});

// logout request handler, passport attaches a logout() function to the req object,
// and we call this to logout the user, same as destroying the data in the session.
app.get("/logout", function(req, res) {
    req.logout();
    res.send("logout success!");
});

// launch the app
app.listen(8080);
console.log("App running at localhost:8080");
console.log(users);

To run the above from command line assume you’ve saved it to passport_facebook.js and you’ve installed node and npm.

npm install express passport passport-facebook express-session
node passport_auth.js

Then visit these urls to try out
http://localhost:8080/
http://localhost:8080/content
http://localhost:8080/login
http://localhost:8080/content
http://localhost:8080/logout
http://localhost:8080/content

Code explanation
Import necessary modules.

var express  			= require('express'),
	app      			= express(),
	passport 			= require('passport'),
	FacebookStrategy	= require('passport-facebook').Strategy,
	session      		= require('express-session');

Hardcoded users, ideally the users should be stored in a database. The findUser function loops throught the users array and return the user if it finds the user, else return null.

var users = [
{"id":111, "username":"amy", "password":"amyspassword"},
{ 
	"id" : "222",
	"email" : "test@test.com", 
	"name" : "Ben", 
	"token" : "DeSag3sEgaEGaYRNKlQp05@diorw"}
];
function findUser(id) {
	for(var i=0; i<users.length; i++) {
		if(id === users[i].id) {
			return users[i]
		}
	}
	return null;
}

Passport serializes and deserializes user instances to and from the session. Here we are only serialize the id of the user in the session in order to keep the data in session small. When getting the user from the session, we deserialize the user by just getting the entire user object from the above hardcoded users for simplicity. The logic in the deserializeUser can be changed to use the id to retrieving the user from a database.

passport.serializeUser(function (user, done) {
    done(null, users[0].id);
});
passport.deserializeUser(function (id, done) {
    done(null, users[0]);
});

Passport facebook strategy for doing the authentication through facebook. It returns the user profile from facebook, if the user already in the users array, then return the user. If the user is not yet in the users array, then add the user to the users array and return the user. In a real world web application, the users array will be a database instead.

// passport facebook strategy
passport.use(new FacebookStrategy({
    "clientID"        : facebookAuth.clientID,
    "clientSecret"    : facebookAuth.clientSecret,
    "callbackURL"     : facebookAuth.callbackURL
},
function (token, refreshToken, profile, done) {
	var user = findUser(profile.id);
	if (user) {
		console.log(users);
		return done(null, user);
	} else {
		var newUser = {
			"id": 		profile.id,
			"name": 	profile.name.givenName + ' ' + profile.name.familyName,
			"email": 	(profile.emails[0].value || '').toLowerCase(),
			"token": 	token
		};
		users.push(newUser);
		console.log(users);
		return done(null, newUser);
	}
}));

Initialize passposrt and and session for persistent login sessions

app.use(session({
	secret: "tHiSiSasEcRetStr",
	resave: true,
    saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());


Route middleware to ensure user is logged in. Passport attached a handy function isAuthenticated() to the req object, and we can use it to determine if the user is authenticated.

function isLoggedIn(req, res, next) {
    if (req.isAuthenticated())
        return next();

    res.sendStatus(401);
}

Home page

app.get("/", function (req, res) {
	res.send("Hello!");
});

Login page. It’s a simple anchor tag pointing to the /auth/facebook endpoint. In a real world application, the request handler function will usually use a template framework to generate a html code and renders the content of the html to the response.

app.get("/login", function (req, res) {
	res.send("<a href='/auth/facebook'>login through facebook</a>");
});

Facebook login request. The first one send the user to facebook to do the authentication. The second one handles the callback after facebook has authenticated the user and redirect back to the callback url.

app.get("/auth/facebook", passport.authenticate("facebook", { scope : "email" }));
app.get("/auth/facebook/callback",
    passport.authenticate("facebook", {
        successRedirect : "/content",
        failureRedirect : "/"
}));

Content page, it calls the isLoggedIn function defined above first. If the user is logged in, then proceed to the request handler function, else the isLoggedIn will send 401 status instead.

app.get("/content", isLoggedIn, function (req, res) {
	res.send("Congratulations! you've successfully logged in.");
});

Logout request handler, passport attaches a logout() function to the req object, and we call this to logout the user, same as destroying the data in the session.

app.get("/logout", function(req, res) {
    req.logout();
    res.send("logout success!");
});

Launch the app 🙂

app.listen(8080);
console.log("App running at localhost:8080");
console.log(users);

Search within Codexpedia

Custom Search

Search the entire web

Custom Search