DEV Community

Cover image for JWT In Plain English
Greg
Greg

Posted on

JWT In Plain English

TLDR;

  1. Install json web token npm i jsonwebtoken
  2. Require
  3. Generate token
  4. Sign the json web token with the users id, secret and expiration time
  5. Check the token in Postman, Database, and jwt.io
  6. Build a route to protect and create the logic
  7. Create middleware to verify the token for the current user
  8. Place middleware into the desired route to protect

What is a json web token?

A secure way to access protected routes through authenticating users and sharing information.
The issuer uses a secret to sign the JWT. The receiver will verify the signature to make sure the token hasn’t been altered after it was signed by the issuer.

Context

I am building a MERN (MongoDB, Express, React, Node) app that will have a user register as a new user or login if the user already exists. The user's information gets stored in the MongoDB database via Express and Node on the Backend. I am utilizing the MVC (Model-view-controller) pattern for separation of concerns, see the link for more info on MVC.

Versions

  • node v16.13.1
  • bcryptjs v^2.4.3,
  • express v^4.17.3,
  • express-async-handler v^1.2.0,
  • jsonwebtoken v^8.5.1,
  • mongoose v^6.2.10

Drilling down further in this demo will only need to focus on the
userController.js, authMiddleware.js, and userRoutes.js see the file structure below:

client also known as view
client
backend
   |config
   |-controller        
         |-userController.js
   |-middleware
         |-authMiddleware.js
   |models
   |-routes
         |-userRoutes.js
   |server
.env
.gitignore
package-lock.json
package.json
Enter fullscreen mode Exit fullscreen mode

Once you have created a user in your database you will need login typically with a password and email. I use bcryptjs to salt my users password from plaintext to a hashed password and confirmed this on Postman and in the database MongoDB. Now you are ready to create the json web token.


Set up the json web token

Step 1

Install

In your cli run npm i jsonwebtoken check if it's in your package.json "jsonwebtoken": "^8.5.1"


Step 2

Require

In your controller folder, open a file with the users routes logic. I called mine userController.js bring in const jwt = require ('jsonwebtoken')
See my code:

backend>controllers>userController.js

userContoller.js

// Simple middleware for handling exceptions inside of async express routes and passing them to your express error handlers. Need to wrap entire function
const asyncHandler = require('express-async-handler');

// Use bcrypt to hash the password
const bcrypt = require('bcryptjs');

// Bring in the user model
const User = require('../models/userModel');

// Bring in jsonwebtoken
const jwt = require ('jsonwebtoken')
Enter fullscreen mode Exit fullscreen mode

Step 3

Generate A Token

Generate a token and pass the users id. The generateToken function will return a .sign method

userContoller.js

const generateToken = (id)=> {
return jwt.sign({})
}
Enter fullscreen mode Exit fullscreen mode

Step 4

Sign The Token

The returned .sing method will take in 3 arguments

  • users id, an object
  • secret (can be anything and should be hidden in your .env)
  • expiration time, an option object
userContoller.js

const generateToken = (id)=> {
return jwt.sign({id},process.env.JWT_SECRET,{expiresIn:'4d'})
}
Enter fullscreen mode Exit fullscreen mode

Step 5

Put It Together
The token needs to be generated with the users id. In my user collection in MonogDB this would be user._id and must be signed. In the register and login route there will be a res.json({}) that gets returned and in the object will have token:generateToken(user._id)

backend>controllers>userController.js

userContoller.js

const registerUser=()=> {
code logic...
res.status(200).json({
  // user in mongodb stores id as _id
  _id: user._id,
  name: user.name,
  email: user.email,
  token:generateToken(user._id)
  });
}

const loginUser=()=> {
code logic...
res.status(200).json({
  // user in mongodb stores id as _id
  _id: user._id,
  name: user.name,
  email: user.email,
  token:generateToken(user._id)
  });
}
Enter fullscreen mode Exit fullscreen mode

The generate function

backend>controllers>userController

userContoller.js

// GENERATE TOKEN
const generateToken = (id) => {
  return jwt.sign({ id }, process.env.JWT_SECRET, {
    expiresIn: '4d'
  });
};
Enter fullscreen mode Exit fullscreen mode

Step 6

Check For Confirmation
I deleted my test user from my database and registered a new user with Postman to manually verify the token was passed http://localhost:5000/api/users
Postman
Inside the token is the users id. Manually verify it, copy and paste your token into jwt.io the field on the left and on the right you will see the payload
jwt.io

The payload circled in red should match the _id in Postman and the _id in your database.

MongoDB

Halfway Home

Now that we created a token how do we use it? The protected route functionality can be achieved in several ways.
Here's how I would do it: build a route, create the logic, create middleware, bring the middleware into the route to protect.

  • Build a route I want to protect called router.get('/me', getUser); in the routes folder in userRoutes.js, later we will come back to this file and add the middleware.

backend>routes>userRoutes.js

userRoutes.js

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

// bring logic from the controllers folder
const { registerUser, loginUser, getUser } = require('../controllers/userController');

// example: router.method(path, logic)
router.post('/', registerUser);
router.get('/login', loginUser);

// protected route, will add protect middleware last
router.get('/me', getUser);

module.exports = router;
Enter fullscreen mode Exit fullscreen mode
  • Create getUser logic in the controllers like this

backend>controllers>userController.js

userController.js

const asyncHandler = require('express-async-handler');
const bcrypt = require('bcryptjs');
const User = require('../models/userModel');
const jwt = require('jsonwebtoken');

const registerUser=()=> {
code logic...
}

const loginUser=()=> {
code logic...
}

const getUser = asyncHandler(async (req, res) => {
  const user = {
    id: req.user._id,
    email: req.user.email,
    name: req.user.name
  };
  res.status(200).json(user);
});

const generateToken = (id) => {
  return jwt.sign({ id }, process.env.JWT_SECRET, {
    expiresIn: '4d'
  });
};

module.exports = {
  loginUser,
  registerUser,
  getUser
};
Enter fullscreen mode Exit fullscreen mode
  • Add a piece of middleware called authMiddleware.js with a function called protect to verify the token will get passed into the headers and return only the specific user info for that token.

backend>middleware>authMiddleware.js

authMiddleware.js
// *MIDDLEWARE FOR PROTECTED ROUTES
const jwt = require('jsonwebtoken');
const asyncHandler= require('express-async-handler');
const User = require('../models/userModel');

const protect= asyncHandler(async(req, res, next)=> {
    let token;

// Bearer token
// Check for token in the headers for authorization and Bearer token
if(req.headers.authorization && req.headers.authorization.startsWith('Bearer')){
try {
    // Get the token from the header
    // it comes back as "Bearer token" must split at space
    // split will return[Bearer token] and need the second item
    token= req.headers.authorization.split(' ')[1]

    // Verify the token value has not changed and the secret is the same
    const decoded = jwt.verify(token, process.env.JWT_SECRET)

    // Return the user from the user id in the token minus the password
    req.user =await User.findById(decoded.id).select('-password');

next()

} catch (error) {
console.log(error);
res.status(401)
throw new Error('Not Authorized')
}
}

//  If there is no token in the first place
if(!token){
    res.status(401)
    throw new Error('Not Authorized')
}

})
module.exports = {protect}
Enter fullscreen mode Exit fullscreen mode
  • Lastly bring the protect function middleware into the userRoutes.js.

backend>routes>userRoutes.js

userRoutes.js

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

// Bring logic from the controllers folder
const { registerUser, loginUser, getUser } = require('../controllers/userController');

// Bring in middleware to protect routes
const {protect}= require('../middleware/authMiddleware');

// example: router.method(path, logic)
router.post('/', registerUser);
router.get('/login', loginUser);

// protected route
router.get('/me',protect, getUser);

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Finally check the protected route through Postman to verify it's protected. You'll want to use the Auth tab and paste your token in from your login route
Bearer token

We are finished here!

Thank you for your time. 

Happy Coding!

Links 🔗

json web token
jwt.io
MVC
Postman
Express-Async-Handler
bcryptjs
MongoDB
Node

Social ❤️

Twitter
Linkedin
Portfolio
Github

Top comments (0)