[Best-Practices] Securing NodeJS Express APIs with JWT Authentication and custom Authorization

Overview

A Node.js library for use as Express middleware to secure endpoints with JWTs. The implementation uses a JWT endpoint of an Authorization Server to get the keys required for verification of the token signature. There is also an example Express app that shows how to use the library.

Package: https://www.npmjs.com/package/jsonwebtoken

Using the JSON web token, we can simply authenticate each and every request on our server. As a standard / best practice, we can use JWT (JSON web token) middleware to validate all requests.

JWT Middleware

  const jwt = require('jsonwebtoken')

  module.exports = (expectedRole) => (req, res, next) => {

  const authHeader = req.get('Authorization')
  if (!authHeader) {
    const error = new Error('Not authenticated.')
    error.statusCode = 401
    throw error
  }

  const token = authHeader.split(' ')[1]
  if (!token) {
    const error = new Error('Not authenticated.')
    error.statusCode = 401
    throw error
  }

  let decodedToken
  try {
    decodedToken = jwt.verify(token, process.env.SECRET_KEY)
  } catch (error) {
    error.statusCode = 401
    throw error
  }

  if (!decodedToken) {
    const error = new Error('Not authenticated.')
    error.statusCode = 401
    throw error
  }

  const role = decodedToken.role

  const authorised = expectedRole.includes(role)
  if (!authorised) {
    const error = new Error('Not authorised.')
    error.statusCode = 401
    throw error
  }

  req.user = decodedToken
  next()
}

This middleware has been prepared and exported. Therefore, we need to include it in our routes file and pass it to the expected role, so in our JWT middleware, we will validate the request with the JWT token, then verify that the user has access to an expected role (this role saved in the database) to this endpoint.

Routes File

 const express = require('express')
 const router = express.Router()

 const auth = require('./auth/index')
 const admin = require('./admin/index')
 const common = require('./common/index')
 const authorize = require('../middleware/jwtAuth')

 router.use('/auth', auth)
 router.use('/admin', authorize(['admin']), admin)
 router.use('/common', authorize(['admin', 'user']), common)

 module.exports = router 

Now that we have set up our authentication and authorization middleware in our routes, we are passing the required role to access these routes. These roles will be checked against our user role.

Our middleware simply next() the request if the user has a valid JWT token and is authorized to access this route, otherwise, it will throw the global error that is caught by the express global error handler.

Leave a Comment

Your email address will not be published. Required fields are marked *

Need Help?