In previous post, we’ve known how to build Token based Authentication & Authorization with Node.js, JWT and MongoDB. This tutorial will continue to make JWT Refresh Token in the Node.js Express Application. You can know how to expire the JWT, then renew the Access Token with Refresh Token.
Related Posts:
– Node.js, Express & MongoDb: Build a CRUD Rest Api example
– How to upload/store images in MongoDB using Node.js, Express & Multer
– Using MySQL/PostgreSQL instead: JWT Refresh Token implementation in Node.js example
Associations:
– MongoDB One-to-One relationship with Mongoose example
– MongoDB One-to-Many Relationship tutorial with Mongoose examples
– MongoDB Many-to-Many Relationship with Mongoose examples
The code in this post bases on previous article that you need to read first:
Node.js + MongoDB: User Authentication & Authorization with JWT
Contents [hide]
Overview of JWT Refresh Token with Node.js example
We already have a Node.js Express & MongoDB application in that:
- User can signup new account, or login with username & password.
- By User’s role (admin, moderator, user), we authorize the User to access resources
With APIs:
Methods | Urls | Actions |
---|---|---|
POST | /api/auth/signup | signup new account |
POST | /api/auth/signin | login an account |
GET | /api/test/all | retrieve public content |
GET | /api/test/user | access User’s content |
GET | /api/test/mod | access Moderator’s content |
GET | /api/test/admin | access Admin’s content |
For more details, please visit this post.
We’re gonna add Token Refresh to this Node.js & JWT Project.
The final result can be described with following requests/responses:
– Send /signin
request, return response with refreshToken
.
– Access resource successfully with accessToken
.
– When the accessToken
is expired, user cannot use it anymore.
– Send /refreshtoken
request, return response with new accessToken
.
– Access resource successfully with new accessToken
.
– Send an expired Refresh Token.
– Send an inexistent Refresh Token.
– Axios Client to check this: Axios Interceptors tutorial with Refresh Token example
– Using React Client:
– Or Vue Client:
– Angular Client:
Flow for JWT Refresh Token implementation
The diagram shows flow of how we implement Authentication process with Access Token and Refresh Token.
– A legal JWT
must be added to HTTP Header if Client accesses protected resources.
– A refreshToken
will be provided at the time user signs in.
How to Expire JWT Token in Node.js
The Refresh Token has different value and expiration time to the Access Token.
Regularly we configure the expiration time of Refresh Token longer than Access Token’s.
Open config/auth.config.js:
module.exports = {
secret: "bezkoder-secret-key",
jwtExpiration: 3600, // 1 hour
jwtRefreshExpiration: 86400, // 24 hours
/* for test */
// jwtExpiration: 60, // 1 minute
// jwtRefreshExpiration: 120, // 2 minutes
};
Update middlewares/authJwt.js file to catch TokenExpiredError
in verifyToken()
function.
const jwt = require("jsonwebtoken");
const config = require("../config/auth.config");
const db = require("../models");
...
const { TokenExpiredError } = jwt;
const catchError = (err, res) => {
if (err instanceof TokenExpiredError) {
return res.status(401).send({ message: "Unauthorized! Access Token was expired!" });
}
return res.sendStatus(401).send({ message: "Unauthorized!" });
}
const verifyToken = (req, res, next) => {
let token = req.headers["x-access-token"];
if (!token) {
return res.status(403).send({ message: "No token provided!" });
}
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return catchError(err, res);
}
req.userId = decoded.id;
next();
});
};
Create Refresh Token Model
This Mongoose model has one-to-one relationship with User
model. It contains expiryDate
field which value is set by adding config.jwtRefreshExpiration
value above.
There are 2 static methods:
createToken
: useuuid
library for creating a random token and save new object into MongoDB databaseverifyExpiration
: compareexpiryDate
with current Date time to check the expiration
const mongoose = require("mongoose");
const config = require("../config/auth.config");
const { v4: uuidv4 } = require('uuid');
const RefreshTokenSchema = new mongoose.Schema({
token: String,
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
expiryDate: Date,
});
RefreshTokenSchema.statics.createToken = async function (user) {
let expiredAt = new Date();
expiredAt.setSeconds(
expiredAt.getSeconds() + config.jwtRefreshExpiration
);
let _token = uuidv4();
let _object = new this({
token: _token,
user: user._id,
expiryDate: expiredAt.getTime(),
});
console.log(_object);
let refreshToken = await _object.save();
return refreshToken.token;
};
RefreshTokenSchema.statics.verifyExpiration = (token) => {
return token.expiryDate.getTime() < new Date().getTime();
}
const RefreshToken = mongoose.model("RefreshToken", RefreshTokenSchema);
module.exports = RefreshToken;
Don’t forget to export this model in models/index.js:
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const db = {};
db.mongoose = mongoose;
db.user = require("./user.model");
db.role = require("./role.model");
db.refreshToken = require("./refreshToken.model");
db.ROLES = ["user", "admin", "moderator"];
module.exports = db;
Node.js Express Rest API for JWT Refresh Token
Let’s update the payloads for our Rest APIs:
– Requests:
- { refreshToken }
– Responses:
- Signin Response: { accessToken, refreshToken, id, username, email, roles }
- Message Response: { message }
- RefreshToken Response: { new accessToken, refreshToken }
In the Auth
Controller, we:
- update the method for
/signin
endpoint with Refresh Token - expose the POST API for creating new Access Token from received Refresh Token
controllers/auth.controller.js
const config = require("../config/auth.config");
const db = require("../models");
const { user: User, role: Role, refreshToken: RefreshToken } = db;
const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
...
exports.signin = (req, res) => {
User.findOne({
username: req.body.username,
})
.populate("roles", "-__v")
.exec(async (err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
if (!user) {
return res.status(404).send({ message: "User Not found." });
}
let passwordIsValid = bcrypt.compareSync(
req.body.password,
user.password
);
if (!passwordIsValid) {
return res.status(401).send({
accessToken: null,
message: "Invalid Password!",
});
}
let token = jwt.sign({ id: user.id }, config.secret, {
expiresIn: config.jwtExpiration,
});
let refreshToken = await RefreshToken.createToken(user);
let authorities = [];
for (let i = 0; i < user.roles.length; i++) {
authorities.push("ROLE_" + user.roles[i].name.toUpperCase());
}
res.status(200).send({
id: user._id,
username: user.username,
email: user.email,
roles: authorities,
accessToken: token,
refreshToken: refreshToken,
});
});
};
exports.refreshToken = async (req, res) => {
const { refreshToken: requestToken } = req.body;
if (requestToken == null) {
return res.status(403).json({ message: "Refresh Token is required!" });
}
try {
let refreshToken = await RefreshToken.findOne({ token: requestToken });
if (!refreshToken) {
res.status(403).json({ message: "Refresh token is not in database!" });
return;
}
if (RefreshToken.verifyExpiration(refreshToken)) {
RefreshToken.findByIdAndRemove(refreshToken._id, { useFindAndModify: false }).exec();
res.status(403).json({
message: "Refresh token was expired. Please make a new signin request",
});
return;
}
let newAccessToken = jwt.sign({ id: refreshToken.user._id }, config.secret, {
expiresIn: config.jwtExpiration,
});
return res.status(200).json({
accessToken: newAccessToken,
refreshToken: refreshToken.token,
});
} catch (err) {
return res.status(500).send({ message: err });
}
};
In refreshToken()
function:
- Firstly, we get the Refresh Token from request data
- Next, get the
RefreshToken
object {id
,user
,token
,expiryDate
} from raw Token usingRefreshToken
model static method - We verify the token (expired or not) basing on
expiryDate
field. If the Refresh Token was expired, remove it from MongoDB database and return message - Continue to use user
_id
field ofRefreshToken
object as parameter to generate new Access Token usingjsonwebtoken
library - Return { new
accessToken
,refreshToken
} if everything is done - Or else, send error message
Define Route for JWT Refresh Token API
Finally, we need to determine how the server with an endpoint will response by setting up the routes.
In routes/auth.routes.js, add one line of code:
...
const controller = require("../controllers/auth.controller");
module.exports = function(app) {
...
app.post("/api/auth/refreshtoken", controller.refreshToken);
};
Conclusion
Today we’ve learned JWT Refresh Token implementation in just a Node.js example using Express Rest Api and MongoDB. You also know how to expire the JWT Token and renew the Access Token.
The code in this post bases on previous article that you need to read first:
Node.js + MongoDB: User Authentication & Authorization with JWT
If you want to use MySQL/PostgreSQL instead, please visit:
JWT Refresh Token implementation in Node.js example
You can test this Rest API with:
– Axios Client: Axios Interceptors tutorial with Refresh Token example
– React Client:
– Vue Client:
– Angular Client:
Happy learning! See you again.
Further Reading
- https://www.npmjs.com/package/express
- http://expressjs.com/en/guide/routing.html
- In-depth Introduction to JWT-JSON Web Token
- https://mongoosejs.com/docs/queries.html
- https://mongoosejs.com/docs/api/model.html
Fullstack CRUD application:
– MEVN: Vue.js + Node.js + Express + MongoDB example
– MEAN:
Angular 8 + Node.js + Express + MongoDB example
Angular 10 + Node.js + Express + MongoDB example
Angular 11 + Node.js + Express + MongoDB example
Angular 12 + Node.js + Express + MongoDB example
– MERN: React + Node.js + Express + MongoDB example
Source Code
You can find the complete source code for this tutorial on Github.
from:JWT implementation with Refresh Token in Node.js example | MongoDB – BezKoder