Skip to content

Latest commit

 

History

History
176 lines (117 loc) · 6.17 KB

File metadata and controls

176 lines (117 loc) · 6.17 KB

Passport.js Demo: JWT Strategy

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.

Installing NPM packages

We need to install two additional packages first.

npm install passport passport-jwt

passport.js configuration to retrieve JWT from cookies

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

Configure how to extract the JWT tokens from requests

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.

The callback function to process JWT payload

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 to passport 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 calling done(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.

Initialize passport.js in app.js

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());

Use passport.js as middleware for authentication

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 to false because we are tracking session with JWT token, instead of cookies with session ID.

Use req.user in a route handler protected by passport.js

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.

Sample Codes

You can find a sample solution with the changes described above.

Another Example

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

Resources