Passport js is a really flexible and unobtrusive authentication system for Node JS. It vastly simplifies authentication and can act as an auth session manager, or as an auth middleware.
The package implements various authentication "Strategies", each supplied as a separate package, which is installed separately and configured as an extension to the base package.
In using the documentation for this package though, I quickly found that its examples only depict an implementation using a document database, most likely MongoDB with Mongoose. In my implementation, as I was using Sequelize with PostgreSQL, I had to modify the setup, for it to work properly.
It works by importing the base "passport" package, and then the required strategy package, then configuring the imported "passport" to use the imported strategy, as below:
const passport = require('passport')
const LocalStrategy = require('passport-local').Strategy
const bcrypt = require('bcryptjs')
const db = require('../../models')
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
},
function (username, password, done) {
return db.User.findOne({ where: { email: username },
include: { model: db.Role, attributes: ['name'] } })
.then(user => {
if (!user) {
return done(null, false, { message: 'Incorrect email!' })
}
return bcrypt.compare(password, user.password)
.then(result => {
if (!result) {
return done(null, false, { message: 'Incorrect password!' })
}
return done(null, user)
})
})
.catch(err => {
return done(err)
})
})
)
As seen in the code above, the base passport package, and the strategy (local strategy here) are imported, and then the passport object is configured like you would a route, to use the strategy, with the strategy receiving a function telling how to process the inputs. Note that the fields could be mapped to whatever fields are present in your input request, which is the first passed object as seen.
The configuration function for the local strategy receives three arguments, the username, the password, and a done function. The general gist of the function is that it first hits the database to find the user, based on the input username. If the user is not found, it throws an error as seen in the if(!user)...
block. Next, it compares the password supplied against the stored password, and if they match returns a user which is then stored in the request, and available as req.user
. If the password does not match the stored password, an error is thrown.
For the password comparison, I saw that the documentation seemed to have added a "verifyPassword" function to the user model which was then called inside the configuration function. That is a perfectly good solution, but to make it more explicit, I chose to go the route of having all the logic in the configuration, to more clearly know what was doing what.
Using this as a middleware on any route means that it throws a 401 (Unauthorized) error whenever authentication fails.
I have also used the JWT strategy in the same project, and found that the strategy configuration function has to be rewritten in this way, with .then blocks to work properly.