PassportJS: Using multiple strategies on the same endpoint
You've probably run into PassportJS if you've had to do authentication in Javascript apps. Coming from a Ruby on Rails background, I'm used to Devise, which provided a similar pluggable framework for authentication.
For things like authentication, it's usually a terrible idea to roll your own, which is why you look for frameworks like these as soon as you start having to manage users and logins. They help guide you away from doing stupid things.
Like deciding who needs a session token and simply setting a loggedIn
flag on the client side to expose unprotected admin functionality on a server. Yep, I've seen that.
There are probably some limits to this, but generally the more aspects of authentication the framework/library takes care of, the better.
The documentation for PassportJS is pretty good for basic use cases, but it does start to get challenging once you get past those. I think some of this also has to do with getting used to the design patterns used by web frameworks like ExpressJS.
One problem in particular that I ran into: While there were plenty of examples floating around on how to authenticate with multiple methods (Twitter, Google, etc.) and then operate with a normal session, it was a lot harder to find examples of how to use multiple authentication strategies on the same endpoint.
Why would you want to do this? Well, say you want to enable either browser based access (via cookies) or some sort of API key based access to the same endpoints. This is becoming more and more common as websites strive to integrate with other tools.
What isn't made completely clear is that sessions are just another authentication strategy in the PassportJS world. passport.session()
is equivalent to passport.authenticate('session')
. So, when you login with a username and password, you aren't just authenticating once. It's just that the endpoint you visit to login requires a username/password for its authentication strategy, and then all of the other endpoints require the session cookie as their authentication strategy.
So when you start talking about using an API key, which you pass with every API request, you're simply looking at choosing between two strategies on the same endpoint.
Then you need to think a little about what's really going on with Express middleware. There's nothing magic about it. It's really just a function that takes a request and a response and the next middleware function (which you can just pass on as a parameter in the authentication function). So that means you're free to make your own middleware that wraps however many authentication functions you want, choosing which one to use based on some aspect of the request itself.
const sessionAuthentication = passport.authenticate('session')
const apiKeyAuthentication = passport.authenticate('headerapikey', { session: false })
app.use((req, res, next) => {
let authenticate = sessionAuthentication
if (req.headers.authorization) {
authenticate = apiKeyAuthentication
}
authenticate(req, res, next)
})
Coming from the Rails world, it's a bit of a shock (at least for me) to be playing around this early at this level. Rails and its focus on convention over configuration, plus the plethora of meta programming magic that Ruby puts at our fingertips, makes it easier to have things feel like they're hooking themselves together. That's great, and it gets you started really quickly in a lot of areas, but it also makes it easy to forget what we're really doing, which in some cases is pretty simple.
Which one's better? Ah... I'm not gonna go down that road. Rails is certainly more declarative... ExpressJS feels more like regular programming. Rails probably keeps you from shooting yourself in the foot in a few ways that ExpressJS won't. But once you get used to ExpressJS' style, it'll likely be an easier apply your tried and true programming knowledge when you run into missing functionality. More boilerplate. Less magic. Less need to hunt through code and documentation when the magic doesn't work. At this point in my career, I'm a bit more comfortable about what's happening behind the scenes, so working with a framework that's a little more direct about it is kind of refreshing. But each can really shine in the right area. Pick your poison.