TLDR;
- Install json web token
npm i jsonwebtoken
- Require
- Generate token
- Sign the json web token with the users id, secret and expiration time
- Check the token in Postman, Database, and jwt.io
- Build a route to protect and create the logic
- Create middleware to verify the token for the current user
- 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
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')
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({})
}
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'})
}
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)
});
}
The generate function
backend>controllers>userController
userContoller.js
// GENERATE TOKEN
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: '4d'
});
};
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
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
The payload circled in red should match the _id in Postman and the _id in your database.
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 inuserRoutes.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;
- 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
};
- Add a piece of middleware called
authMiddleware.js
with a function calledprotect
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}
- 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;
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
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
Top comments (0)