User authorization with JSON Web Tokens in a MERN Stack application

A JSON Web Token (JWT) is a great way to allow users to access protected resources including routes because each subsequent request the user makes will include the JWT created when they logged in.

There are three parts to a JWT: A header, a payload, and a signature and each of these is separated by a dot. You can dive into the details here.

Here is some code from a simple MERN Stack application I’m building for my developer portfolio. It’s a contact manager called Contac. You can access the repo on GitHub here.

In this file, I built a piece of middleware for use across the application with the responsibility of verifying JWTs when users try to access protected routes.

const jwt = require('jsonwebtoken');
const config = require('config');

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

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

  try {
    const decoded = jwt.verify(token, config.get('jwtSecret'));

    req.user = decoded.user;
    next();
  } catch (err) {
    res.status(401).json({ msg: 'Token is not valid' });
  }
};

In this file, you can see the middleware being implemented on line 11 where it’s included as the second parameter and on line 54 the sign method is called on the JWT after it’s been matched to the logged-in user and verified.

const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const config = require('config');
const auth = require('../middleware/auth');
const { check, validationResult } = require('express-validator');

const User = require('../models/User');

router.get('/', auth, async (req, res) => {
  try {
    const user = await User.findById(req.user.id).select('-password');
    res.json(user);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server Error');
  }
});

router.post(
  '/',
  [
    check('email', 'Please include a valid email.').isEmail(),
    check('password', 'Password is required.').exists()
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { email, password } = req.body;

    try {
      let user = await User.findOne({ email });

      if (!user) {
        return res.status(400).json({ msg: 'Invalid Credentials' });
      }

      const isMatch = await bcrypt.compare(password, user.password);

      if (!isMatch) {
        return res.status(400).json({ msg: 'Invalid Credentials' });
      }

      const payload = {
        user: {
          id: user.id
        }
      };

      jwt.sign(
        payload,
        config.get('jwtSecret'),
        {
          // REMINDER: Change prior to deployment.
          expiresIn: 360000
        },
        (err, token) => {
          if (err) throw err;
          res.json({ token });
        }
      );
    } catch (err) {
      console.err(err.message);
      res.status(500).send('Server Error');
    }
  }
);

module.exports = router;
Show Comments

Get the latest posts delivered right to your inbox.