In the previous section, we learned how to use express-jwt
to validate JWT tokens. We could do the same with another library called passport-jwt. It's a strategy for passport.js to handle authentication with JWT tokens.
Let's modify the codes we get from the previous JWT demo (where we use cookies to store JWT token), taking out express-jwt
and replace it with passport
and passport-jwt
.
We need to install two additional packages first.
npm install passport passport-jwt
We an configure passport.js with a JWT
strategy which knows how to validate JWT tokens in HTTP requests and only grant access to protected resources if valid JWT tokens present.
Create a new file passport_middleware.js
in the middleware
folder:
const passport = require("passport");
const passportJWT = require("passport-jwt");
const JwtStrategy = passportJWT.Strategy;
const User = require("../model/user");
const { secret } = require("../../config/jwt");
const cookieExtractor = function(req) {
var token = null;
if (req && req.cookies) {
token = req.cookies["jwt"];
}
return token;
};
const jwtOptions = {
jwtFromRequest: cookieExtractor,
secretOrKey: secret
};
const saveUserInRequest = async (jwt_payload, done) => {
try {
const user = await User.findOne({ _id: jwt_payload.userid });
if (user) {
done(null, user);
} else {
done(null, false);
}
} catch (err) {
done(err, false);
}
};
const jwtStrategy = new JwtStrategy(jwtOptions, saveUserInRequest);
passport.use(jwtStrategy);
module.exports = {
passport
};
When we create an instance of JwtStrategy, we need to provide two arguments:
- Options to control how to extract JWT tokens from requests
- A callback function which is invoked when the JWT token is found, verified
We need to provide two configurations:
- jwtFromRequest: this is a callback function to extract JWT token from requests.
- secretOrKey: this is the secret used to sign and verify JWT tokens.
Once passport
extracts the JWT token from requests and verifies its signature, it invokes a callback function to do some post-processing.
The callback function takes in two arguments:
- the JWT token payload
- a callback function (usually named
done
) which can be called to pass the control back topassport
library
In the example given above, the saveUserInRequest
function looks up the user by taking the user ID from the JWT payload.
- If the user is found, it pass the user to the
passport
library via callingdone(null, user)
- If the user is not found, it calls
done(null, false)
to indicate the user is not found - If there are any unexpected errors, it calls
done(error, false)
to pass the error to passport.
Once the passport library get the user
from the done(null, user)
call, it saves the user in the req.user
field. Then the route handlers protected by this passport middleware can get user information by checking req.user
.
In order to use the passport middleware we configured above, we need to declare it in app.js
, in the "middlewares" section:
const { passport } = require("./middleware/passport_middleware");
app.use(passport.initialize());
With the passport.js configuration with JWT
strategy, we can protect our routes in the following way.
In the user_api.js
, we used to protect the /change_password
route with express-jwt
middleware. But now we can use the passport middleware instead.
Change the code from:
const jwt_validation = require("../middleware/jwt_middleware");
router.put(
"/change_password",
jwt_validation.required,
handleAsyncError(userHandler.changePassword)
);
to:
const passport = require("passport");
router.put(
"/change_password",
passport.authenticate("jwt", { session: false }),
handleAsyncError(userHandler.changePassword)
);
Here when we call the passport middleware, we pass in two arguments:
- "jwt", this indicates we want to use the JWT strategy for passport.js
- we set the
session
option tofalse
because we are tracking session with JWT token, instead of cookies with session ID.
Once the authentication is done successfully, the passport.js middleware saves the user information in the req.user
field.
In the user_handler.js
, we update the first few lines of changePassword
function accordingly:
From:
const userId = req.user.userid;
const user = await User.findById(userId);
to:
// const userId = req.user.userid;
// const user = await User.findById(userId);
const user = req.user;
In the passport middleware configuration, we have that saveUserInRequest
function, which lookup the user based his ID already, so the req.user
field contains the user loaded from database. We don't need to lookup the user again in this handler.
Note: pay attention to this difference! When we were using the express-jwt
library for authentication, express-jwt
saves the JWT token payload in the req.user
field, but in this new implementation, we save the user model (retrieved from database) in the req.user
field.
You can find a sample solution with the changes described above.
Here is another tutorial which shows the usage of both local strategy and JWT strategy:
- local strategy is used to authenticate user with user name and password
- JWT strategy is used to validate JWT tokens in the subsequent requests