In this weeks blog, I am going to go over how to utilize JWT tokens to authenticate a user in the backend, using Node.js. In order to utilize jwt, the package needs to be installed by running npm i jsonwebtoken. I will mostly be focusing to jwt, but these are some of the other packages I installed and dependencies I required in the auth routes page: express, bcryptjs, config, express-validator, express.Router(), the User model, and some custom auth middleware. Below is the auth middleware, which accepts three arguments: request, response and next. Next (next())is a callback function that gets run once the middleware completes so that the application doesn’t get “stuck” in the function.
const jwt = require('jsonwebtoken')const config = require('config')module.exports = function(req, res, next){// Get token from headerconst token = req.header('x-auth-token')// Check if no tokenif(!token){return res.status(401).json({msg: 'No token, authorization denied'})}//Verify tokentry {const decoded = jwt.verify(token, config.get('jwtSecret'))req.user = decoded.usernext()} catch(err){res.status(401).json({msg: 'Token is not valid'})}}
What’s happening here is the the token is being retrieved from the header and saved in the “token” variable. The following if statement handles the possibility that there is no token, which will then send a status of 401 (unauthorized) and the middleware will cease. However, if there is a token, it needs to be verified. This verification is occurring in the “try”, where the token is being decoded using verify(). Verify takes in two arguments, the first being the token and the second being the secret. A jwt secret is one that is set by the developer and stored in a git ignore file that can be accessed using the config package. Once the token is verified, the user in the request can be set to the “decoded” user from the token (req.user = decoded.user). Req.user can now be used directly in protected routes. Finally, if there is an error in validating the token, there is a catch statement to send an unauthorized status.
Moving on the the auth file, the first route will be a get request in order to find a user by token.
const express = require('express')const router = express.Router()const bcrypt = require('bcryptjs')const jwt = require('jsonwebtoken')const config = require('config')const { check, validationResult } = require('express-validator')const auth = require('../../middleware/auth')const User = require('../../models/User')
// @route GET api/auth// @desc Get user by token// @access Publicrouter.get('/', auth, async (req, res) => {try {const user = await User.findById(req.user.id).select('-password')res.json(user)} catch(err){res.status(500).send('Server error')}})
router.get takes in three arguments: the path, middleware, and a callback function containing the request and response. To protect routes, all that needs to be done is to add the auth middleware as a second argument in the router.get (or any other CRUD action) function. Then, in the try statement we utilize a mongoose function “findById” to get the user from the database (without the password) and store it in the response. In the next route, the user is authenticated with a post request
// @route POST api/auth// @desc Authenticate User and get token// @access Publicrouter.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.bodytry {let user = await User.findOne({ email })if(!user) {return res.status(400).json({ errors: [{msg: 'Invalid credentials'}] })}const isMatch = await bcrypt.compare(password, user.password)if(!isMatch){return res.status(400).json({ errors: [{msg: 'Invalid credentials'}] })}const payload = {user: {id: user.id}}jwt.sign(payload,config.get('jwtSecret'),{ expiresIn: 36000 },(err, token) => {if(err) throw errres.json({ token })})} catch(err){res.status(500).send('Server error')}})
Here we are setting the payload as a constant that contains the user id (note: we don’t need to do ._id even though a MongoDB database is used — mongoose abstracts the underscore so you can access with just .id). Next, the token is “signed”. This takes an argument of payload, jwt secret, expires in, and a callback function that takes in error and token as arguments. The token is now stored in the response and the user can access protected routes.