Implementing User Authentication in Express-Mongo using JWT

What is JWT?

JWT is a popular method of signing in users in stateless APIs. I have been working on a simple API providing data about tours to its users using a tour database. To access certain routes in API user authentication is required.

How to generate JWT?

JWT which stands for JSON web token works by generating a token(a string). This string can be sent to the user as a cookie and browser will send this cookie to the server on every subsequent request. The user's status can then be checked by validating the token. JWT can be generated using the following method:

exports.signJWT = (id, res) => {
//set cookie options
  const cookieOptions = {
    httpOnly: true,
    expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
  };
//for testing secure option is turned off ass the API is being tested using http
//enabling secure will only send cookie over https
  if (process.env.NODE_ENV === 'production') cookieOptions.secure = true;
//use jwt.sign(payload, secre, options)
  const token = jwt.sign({ id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRE,
  });
//attach cookie to response object
  res.cookie('jwt', token, cookieOptions);
//return the token if needed
  return token;
};

For more reference please visit:

JWT official website

jwt.sign() accepts payload as the first argument. The payload is the data that the app will retrieve when JWT is decoded. In my case the payload includes userId. The secret is used to sign JWT. A JWT can only be decoded using this secret.

Using JWT to verify users

In express a middleware function is best way to verify user identity. The function can be added into the middleware stack in a protected route's route handler.

router.post('/change-password', isLoggedIn, changePassword);

In this case when a user accesses /change-password route, he/she will have to successfully pass isLoggedIn middleware to access the changePassword route handler.

we can implement JWT verification inside isLoggedIn. If user verified successfully we can pass down the execution down the middleware stack, otherwise we can complete the req-res cycle by returning an error response from isLoggedIn.

Here is code implemetation:

In this case jwt is being retrieved from req headers:


exports.isLoggedIn = async (req, res, next) => {
  //this variable is used in catch block to send different status codes
  //in response according to the reason for error
  let statusCode = 200;
  try {
    //--CHECKING IF JWT IS PRESENT IN HEADERS
    const { headers } = req;
    const authHeader = headers.authorization;
    //if 'authorization' is not present in headers -> user is not logged in
    //Access to the protected route is denied
    if (!authHeader || !authHeader.startsWith('Bearer')) {
      statusCode = 401;
      throw new Error('You are not logged in');
    }
    //remove the prefix of Bearer from the token
    const token = authHeader.split(' ')[1];
    if (!token) {
      statusCode = 401;
      throw new Error('You are not logged in');
    }

    //---VERIFYING JWT---
    /**
     * Promisify the async version of jwt.verify()
     * and await the resolved token
     *
     * If token is found to be invalid promise will be rejected and
     * code will fallbak to the catch block which will send a 'failed' respose
     */
    const { id, iat } = await new Promise((resolve, reject) => {
      jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
        if (err) reject(err);
        resolve(decoded);
      });
    });

    //---- VERYFYING THE PAYLOAD FOR USER DETAILS-----
    //if token does not contain 'id' field the payload has been modifies
    //in which case Error is thrown
    if (!id) {
      statusCode = 401;
      throw new Error('Invalid credentails');
    }

    //Check if the user exists using the id from payload
    //if not throw an Error
    const user = await User.findById(id)
    if (!user) {
      statusCode = 401;
      throw new Error('Invalid User!');
    }
    req.user = user;
    next();
  } catch (err) {
    return res.status(statusCode).json({
      status: 'failed',
      message: err.message,
      stack: err,
    });
  }
};

And thats all ! This was a fairly simple implementation of JWT and I the payload can be harnessedto store more data about the user as per app requirements