Some quick tips for Passport

This post is more than 2 years old.

Yesterday I decided to take a look at Passport, an open source library for Node.js focused on authentication. LoopBack supports Passport too, but when I first looked at it, I realized that I knew nothing about Passport itself and it would make sense to try it by itself before I try wrapping it with LoopBack.

At a high level, Passport lets you abstract away some of the details of authentication in your application. It has plugins which allow you to easily add in support for Twitter, Facebook, or Google authentication. (Passport calls these "Strategies", and there are over three hundred of them!)

When I began trying to test Passport, my first question was whether they had some form of simple authentication not tied to an external provider. They did - which is cool - but I really struggled with trying to figure out how to use it. To be fair, everything that confused me was documented. I just couldn't figure it out as is. So what follows are simply a few notes on things that didn't make sense to me.

Local Authentication

As I said - I assumed (hoped!) that Passport would have a 'simple' authentication that I could use while prototyping my app. That way I could build stuff and then drop in Twitter/Facebook/etc later. Turns out I was right - they did support this, and they call it the LocalStrategy. However, the code example was confusing to me. The docs show it as such:


var passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy(
  function(username, password, done) {
    User.findOne({ username: username }, function (err, user) {
      if (err) { return done(err); }
      if (!user) {
        return done(null, false, { message: 'Incorrect username.' });
      }
      if (!user.validPassword(password)) {
        return done(null, false, { message: 'Incorrect password.' });
      }
      return done(null, user);
    });
  }
));

Looking at this, I thought, "Hmm, that looks like Mongo a bit, but they never mentioned using Mongo, so does Passport support a User object?" And I kid you not - I was stuck here for a good hour or so. Turns out, it is Mongo code, and maybe they assume all Node users know Mongo and are familiar with the API, but I wish they had actually said that in the example. Even the validPassword code is weird to me. All in all, it feels like a "real" code sample, and I appreciate that, but without more context it also feels unnecessarily complex. As a blogger, I definitely understand the problem you face when writing docs. You want something useful, something real world, but you also want something the reader can properly grok.

Another thing not spelled out well (or not to me anyway) was the fact that the object you return in the callback can be, as far as I know, anything that represents the user. Basically, you decide what represents the "User object".

Here is how I got my 'fake' login working:


var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
	function(username, password, done) {

		//any username, password=admin
		if(password === 'admin') {
			return done(null, {id:1,name:username});
		} else {
			return done(null, false);
		}
	}
));

I'm not necessarily saying my code is any better, I just wanted to get it working, but hopefully you get the idea. I can no login with any username and a password of admin. Note the "object" I return is completely arbitrary.

Session storage

Passport also supports storing the user information in the session if your app supports it. This is done by a custom serializer/deserializer. Here is what I used:


passport.serializeUser(function(user, cb) {
	cb(null, JSON.stringify(user));
});

passport.deserializeUser(function(packet, cb) {
	cb(null,JSON.parse(packet));
});

In this case, the Mongo code (which again, isn't called out as Mongo) is a bit more clearer:


passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

In this case, they store just the ID and then load it from the database when deserializing. As far as I can tell, this will run on every request when you've logged in.

Am I Logged In?

This too is documented but wasn't clear to me at first. Given a route with a req/res pair of arguments, req.user will exists if the current user is logged in. So here is a simple example where I wrote some middleware to see if logged in and then push to the login page otherwise.


app.get('/', function(req, res) {
	console.log(req.user);
	res.render('index',{message:req.flash('error')});
});

app.post('/login', passport.authenticate('local', 
	{
		failureRedirect:'/',
		failureFlash:'Login failed'
	}), function(req, res) {
	console.log('user',req.user);
	res.redirect('/dashboard');
});

function requireLogin(req,res,next) {
	if(!req.user) return res.redirect('/');
	next();
}

app.get('/dashboard', requireLogin, function(req, res) {
	res.render('dashboard',{user:req.user});
});

Flash messages

Ok, I know most people know that "Flash messages" are simply temporary messages stored in a session, but I always think of Adobe Flash first. Anyway, Passport supports using Flash messages as a way of passing along a message to the user that their authentication passed or failed. This is documented, but I ran into an issue. Flash messages are stored via a key, but the docs don't tell you the name of the key to use to fetch the message. All it says is:

"Setting the failureFlash option to true instructs Passport to flash an error message using the message option set by the verify callback above."

Notice how error is in a different font? That's the clue - you fetch the message by using req.flash('error'). Again - I guess it may be obvious, but this took me a while to get right as well.

More to come

I hope this helps folks. Again, check the docs for more info, and as I play with this more I'll share anything else that trips me up!

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Eric McCormick posted on 6/23/2016 at 8:54 PM

Good info, it sounds like their documentation could use a little updating.

Comment 2 (In reply to #1) by Raymond Camden posted on 6/23/2016 at 9:03 PM

I *should* file a bug report or two.

Comment 3 by Tarabass posted on 6/23/2016 at 9:48 PM

Hi Raymond. I ended up with almost the same, although my middleware is quite different.

Instead of checking req.user is undefined, you can also call "req.isAuthenticated()"

function requireLogin(req, res, next) {
// if user is authenticated in the session, carry on
if(req.isAuthenticated()) {
return next();
}

// if they aren't redirect them to the home page
res.redirect('/');
}

Comment 4 (In reply to #3) by Raymond Camden posted on 6/24/2016 at 1:21 AM

Interesting - is isAuthenticated documented somewhere?

Comment 5 (In reply to #4) by Tarabass posted on 6/24/2016 at 1:01 PM

What do you think? Of course not. It's there for a long time, and still no docs for it. Even their own examples are very outdated.

http://stackoverflow.com/a/...
Switching between strategies is also a thing. I hope to see your solution for that in the next post about passport. If any? I wrote myself some code and want to compare it with your solution..

Comment 6 (In reply to #5) by Raymond Camden posted on 6/24/2016 at 1:03 PM

Well shoot. I'll file a bug report for this.

And yes - I'm still working on the app. I got Twitter Auth working yesterday and it was pretty darn easy actually.

Comment 7 (In reply to #6) by Raymond Camden posted on 6/24/2016 at 1:06 PM
Comment 8 (In reply to #7) by Tarabass posted on 6/24/2016 at 2:56 PM

Good job :)
But why you have to do it after all those years!? Maybe I should participate more often, although I don't feel connected to the OS community very much..

Comment 9 (In reply to #8) by Raymond Camden posted on 6/24/2016 at 3:18 PM

Haven't released many OS projects myself over the years, I can tell you it gets difficult to keep up. Heck, even a 5 minute mod may be hard to do when your motivation is low.

Comment 10 by Simon Prickett posted on 6/29/2016 at 8:00 PM

I used Passport recently on a Node project that had to use the Under Armour API which uses OAuth. Was really simple to get going with as someone already had an off the shelf strategy plugin for Under Armour that just worked.

Comment 11 (In reply to #10) by Raymond Camden posted on 6/29/2016 at 8:12 PM

It's OT I guess - but what in the heck did you use an Under Armour API for?

Comment 12 by Edgar Sanchez posted on 4/26/2017 at 1:20 PM

Paragraph #5 describes perfectly my feelings. Your fake login example saved me an hour (or more). Examples should NOT be tied to Mongo.

Thanks!

Comment 13 (In reply to #12) by Raymond Camden posted on 4/26/2017 at 1:22 PM

Glad I wasn't the only one confused by this. :)

Comment 14 by Sherlock Vilmes Vic Vilmes posted on 12/30/2017 at 4:13 AM

while the official docs are terribly written, thanks for helping out with such clear explanations.

Comment 15 by moy2010 posted on 3/14/2018 at 9:44 AM

Damn, it's hilarious to find someone else that experienced the same problem in pretty much the same way.

I was stucked on the last part (flash messages) and, after a long night, finally found your post. I cannot thank you enough for this, since I can go to sleep now thanks to you.

Thanks a lot, Raymond!

Comment 16 (In reply to #15) by Raymond Camden posted on 3/14/2018 at 12:54 PM

You are most welcome.

Comment 17 by bugler1 posted on 6/28/2018 at 5:41 PM

Thank you very much.
Most people, specially those gurus out there, don't know how to explain things cause everything is so so easy for them that there is really nothing to expalin.

Comment 18 (In reply to #17) by Raymond Camden posted on 6/28/2018 at 5:46 PM

You are very welcome. I am definitely not a guru myself!