Gravatar of Jeroen Jeroen Pelgrims

Token based, sessionless auth using express and passport

Posted on in javascript, node, express, software-development

Problem at hand§

What I wanted to make was a webservice with which I could authenticate through facebook initially and using a token in subsequent requests. I found a bunch of clear information about each individual part but not how to integrate everything, hence this post.
A working example containing all the code can be found on Github.

I'll be using:

  1. express
  2. mongoose: for storing the users, could just have used an array for the example, but this made it a bit more complete.
  3. passport
  4. passport-facebook
  5. passport-http-bearer: token based authentication for express

Facebook auth§

Passport made this incredibly easy with their facebook strategy. This sample code shows that the token returned by Facebook is stored on the user connected to the id of the facebook profile that's returned. For the source of what User.findOrCreate does you can look at the repo

This strategy could be replaced by implementing your own OAuth strategy using passport.

options = {
  clientID: process.env.FACEBOOK_APP_ID,
  clientSecret: process.env.FACEBOOK_APP_SECRET,
  callbackURL: "http://localhost:3000/auth/facebook/callback",
};

passport.use(
  new FacebookStrategy(options, function (
    accessToken,
    refreshToken,
    profile,
    done
  ) {
    User.findOrCreate({ facebookId: profile.id }, function (err, result) {
      if (result) {
        result.access_token = accessToken;
        result.save(function (err, doc) {
          done(err, doc);
        });
      } else {
        done(err, result);
      }
    });
  })
);

Halfway through writing this I regretted not just writing it in CoffeeScript, which was the case for my original code. Because }})}}));
As you see I'm just storing the token returned by Facebook to be used by the client but you could just as well generate your own token here and store that. I'm also storing the token in mongodb here but maybe storing the token in redis and pointing to the user's id would be better. Because with this implementation the user won't be able to be logged in from 2 different clients at once.
(The 2nd time the token will be different and thus overwritten in the DB, invalidating the first client's token).

Making it sessionless§

I had 2 problems with this.

  1. After successfully authenticating with facebook, passport wanted to serialize the user and save hir in the session.
  2. How to return the token to the client. In the documentation of passport-facebook it is only shown how to set static success and failure routes:
app.get(
  "/auth/facebook/callback",
  passport.authenticate("facebook", {
    successRedirect: "/",
    failureRedirect: "/login",
  })
);

I needed a way to add the token to the successRedirect route.

app.get(
  "/auth/facebook",
  passport.authenticate("facebook", { session: false, scope: [] })
);

app.get(
  "/auth/facebook/callback",
  passport.authenticate("facebook", { session: false, failureRedirect: "/" }),
  function (req, res) {
    res.redirect("/profile?access_token=" + req.user.access_token);
  }
);

Note that it is very important that the query parameter is called access_token. I couldn't find this anywhere in the passport-http-bearer docs and had to search to the source code to figure that out.

Authenticating using a token§

The following piece is pretty much a copy from the passport-http-bearer docs.

passport.use(
  new BearerStrategy(function (token, done) {
    User.findOne({ access_token: token }, function (err, user) {
      if (err) {
        return done(err);
      }
      if (!user) {
        return done(null, false);
      }

      return done(null, user, { scope: "all" });
    });
  })
);

Setting up a route to require a token is done like this:

app.get(
  "/profile",
  passport.authenticate("bearer", { session: false }),
  function (req, res) {
    res.send(
      "LOGGED IN as " + req.user.facebookId + ' - <a href="/logout">Log out</a>'
    );
  }
);

We're authenticating using bearer and no session. Easy as that.

Result§

Getting http://localhost:3000/profile will return a 401 since you're not providing a token (using the access_token query parameter).
Getting http://localhost:3000/profile?access_token=<foo>, where <foo> is the correct token stored in database on the user, will give you the expected result.
You can also choose to pass the token in the request body or as a request header.

Don't forget to invalidate the token when implementing the logout handler.

Important§

Because you're sending authentication data with every request it is important that your communications are protected using SSL.

Cordova & Phonegap§

All this redirecting won't work cleanly within a phonegap app. A solution for that can be found here
For now you can check the source of his solution how to make it work, but I plan to make an other blog post in the future explaining it in detail. (The solution is to open an in-app browser window and do the redirecting in there)

This post is written by Jeroen Pelgrims, an independent software developer who runs Digicraft.eu.

Hire Jeroen