Aquasar
  • Home
  • Portfolio
  • Articles
  • Pricing
  • About
  • Contact
WEB DEVELOPMENT |SEO |DIGITAL ADS

Protecting routes with Auth Middleware

Jun 28th, 2019

Alex Quasar

expressmiddlewareprotecting routes

Protecting routes with auth middleware is something that is crucial for any web app.

Here is an implementation of auth middleware that can be used to protect your routes. I have this in the directory, middleware in an auth.js file

 const jwt = require('jsonwebtoken');
const jwtSecret = require('../config/keys').jwtSecret;
const AppError = require('../utils/appError');

module.exports = async function(req, res, next) {
  // Get token from header
  const token = req.header('x-auth-token');

  // Check if not token
  if (!token) {
    return res.status(401).json({ msg: 'No token, authorization denied' });
  }

  // Verify tokens
  try {
    // verify token
    const decoded = jwt.verify(token, jwtSecret);
    // verify user exists
    const user = await User.findById(decoded.user.id).select('name role');
    if(!user){
      return next(new AppError('User and token mismatch!', 401));
    }

    // check if user changed password after token issued
    if(user.changedPasswordAfter(decoded.iat) ) {
      return next( new AppError('Password was recently changed',401));
    }
 
    // data to send back in request from auth middleware
    req.user = user

    // grant access to protected route.
    next();

  } catch (err) {
    res.status(401).json({ msg: 'Token is not valid' });
  }
};

This article will attempt to break down the code above in several steps. You should be familiar with Express and Mongoose however.

  // Get token from header  const token = req.header('x-auth-token'); 

If a user is authenticated than they will have a token. This token is generated when the user is logged into the app. For more information about this visit Logging in users with express

The first thing we do is check to see if we have a token and verify it using jwt.verify.

    // verify token
    const decoded = jwt.verify(token, jwtSecret);

The second thing we do is to make sure that user exists. We do this finding the user by Id. This Id was stored in the payload of the token, and we can decode it to get back the user in our db. Of course this will only work if you have the jwtSecret as well, which is stored in a secure keys file, where the values are never pushed to github!

    if(!user){
      return next(new AppError('User and token mismatch!', 401));
    }

AppError is an extension of the Error object, see link here. Otherwise you could also do.

    if(!user){
      return res.status(401).json({msg:'User and token mismatch'});
    }

Next, we want to make sure the user did not recently change their password. If the password was changed after the token was issued then we will not grant them access to the protected route. On the front end, we can redirect them back to a sign in page. Here decoded.iat is when the jwt token was issued at.

   // check if user changed password after token issued
    if(user.passwordRecentlyChanged(decoded.iat) ) {
      return next( new AppError('Password was recently changed',401));
    }

Where passwordRecentlyChanged is a User method available on all user documents. Inside the User model:

// changed password
UserSchema.methods.passwordRecentlyChanged = function(JWTTimestamp) {
    if(this.passwordChangedAt){
        const passwordChangedAtTimeStamp = parseInt(this.passwordChangedAt.getTime() / 1000,10);
        return passwordChangedAtTimeStamp >= JWTTimestamp;
    }
    return false;
}

If none of those conditions are met, we will set req.user to user.

  // data to send back in request from auth middleware
    req.user = user
    // grant access to protected route.
    next();

This sends back the name and role of the user to the next function and grants access to the protected route. Note, req is an object that is available in the request, response cycle. To this object, add user property and set to user, the document (User instance) from mongoose. I only want to get back name and role, so select method is used.

   const user = await User.findById(decoded.user.id).select('name role');

That is pretty much it for this auth middleware. Here is a simple sample request using the newly created auth middleware.

// Type         :   GET
// Route        :   api/users
// Description  :   Get all users from the database
// Access       :   Only logged in users can do this
router.get('/', auth, async (req, res) => {
    try {

        const users = await User.find().select('-_id name email role');
        res.json(users);
    

    } catch (error) {
        console.error('There was an error', error);
        res.status(500).send('server error');
    }
})

If you want different levels of access for logged in users, see How to created restricted routes for specific users.