Facebook Connect With Passport.js

published on November 23, 2014 in technical

I needed a Facebook authentication implemented with node.js. Seamed ok to use Passport.js for this purpose, it looks liket it is maintained a fairly easy to use.

If I would have to TL;DR it, I initialize passport in my index.js file, then passed it to the routing, where I do the request authentication.

But we're not going for the short version, but for the bit more detailed one.

First, I needed some npm modules, namely:

express = require("express"),
mongoose = require("mongoose"),
MongoStore = require("connect-mongostore")(express),
passport = require("passport"),
LocalStrategy = require("passport-local").Strategy,
FacebookStrategy = require("passport-facebook").Strategy;

I am using Express.js with mongoDb, as you may see above. Then, I configured Passport.js:

passport.use(new FacebookStrategy({
    clientID: "",
    clientSecret: "",
    callbackURL: ""
  },
  function(accessToken, refreshToken, profile, callback) {
    "use strict";
    users.find({
      originalId: profile.id,
      source: "facebook"
    }, function(user) {
      if (user.length !== 0) {
        callback(null, user[0]);
      } else {
        users.create(profile, function(newUser) {
          callback(null, newUser);
        });
      }
    });
  }
));

This instructs passport to use a certain Facebook application, which I have previously created. Also, it retrieves the logged in user either from the the existing ones, or creates one. Both of these are done with the help uf a Users model, which is nothing else but an average Mongoose model.

There are two things, that are really important: when using Passport.js you probably have to implement the serializeUser and deserializeUser methods to allow the lib to do the two of them properly:

passport.serializeUser(function(user, callback) {
  "use strict";
  callback(null, user._id);
});


passport.deserializeUser(function(id, callback) {
  "use strict";
  users.find({
    _id: id
  }, function(user) {
    callback(null, user);
  });
});

Express also needs some configuration, like telling it to use cookies:

app.use(express.cookieParser());
app.use(express.bodyParser());

and persist sessions among requests. This may be achieved in a number of ways. I tried persisting sessions in MongoDb,

app.use(express.session({
  secret: "Secret",
  maxAge: new Date(Date.now() + 3600000),
  store: new MongoStore(
    {
      db: mongoose.connection.db
    },
    function(error) {
      console.log(error || "connect-mongodb setup ok");
    })
  })
);

which I ended up not using, because I felt it slow (due to my hosted MongoDB from MongoLab).
What I ended up using is the plain old cookie store:

app.use(express.cookieSession({
    secret: "Secret",
    cookie: {
      maxAge: 1000*60*60
    }
  })
);

Express also needs these:

app.use(passport.initialize());
app.use(passport.session());

The last thing I did here is pass the initialized passport to the routing:

require("./application/routes") (app, passport);

My routing looks like this:

module.exports = function(app, passport) {
  "use strict";
  var publicController = require("../application/controllers/public") (passport),
    apiController = require("../application/controllers/api") (app),
    auth = require("../application/auth") (app);

  app.get("/auth/facebook/", passport.authenticate("facebook", {
    scope : "email, user_about_me"
  }));

  app.get("/auth/facebook/callback", passport.authenticate("facebook", {
    successRedirect: "/dashboard/",
    failureRedirect: "/connect/"
  }));

  app.get("/dashboard/", auth.isAuthenticated , function(req, res){
    publicController.render(req, res);
  });
	
  app.get("*", publicController.render);
};

The content in my page is handled by one method, which renders looks based on the config file, which cotains the views connected to urls. This way it is fairly easy to display anything.

Currently I have only one url which has to authenticated, that is the dashboard. You can see that I check the auth state right before I render the dashboard. The isAuthenticated method that checks all this, is in the auth.js file:

module.exports = function () {
  "use strict";
  return auth;
};

auth.isAuthenticated = function (req, res, next) {
  "use strict";
  if (req.isAuthenticated()) {
    next();
  } else {
    res.redirect("/connect/");
  }
};

and redirects the user to the connect page, if not logged in.

Ths is basically it, folks. The next step for me is to implement the same with Twitter and Google and write that up here, too.

If you any mistakes, which could happen due to this being my firs node.js authentication, please let me know on twitter and I'll fix it.