We’ve known how to build Token based Authentication & Authorization with Node.js, Express and JWT. This tutorial will continue to implement JWT Refresh Token in the Node.js Application. You can know how to expire the JWT, then renew the Access Token with Refresh Token.
Using MongoDB instead:
JWT Refresh Token implementation in Node.js and MongoDB
Related Posts:
– Node.js Rest APIs example with Express, Sequelize & MySQL
– Node.js Express File Upload Rest API example using Multer
Associations:
– Sequelize One-to-Many Association example
– Sequelize Many-to-Many Association example
Deployment:
– Deploying/Hosting Node.js app on Heroku with MySQL database
– Dockerize Node.js Express and MySQL example – Docker Compose
The code in this post bases on previous article that you need to read first:
– Node.js JWT Authentication & Authorization with MySQL example
– Node.js JWT Authentication & Authorization with PostgreSQL example
Contents
Overview of JWT Refresh Token with Node.js example
We already have a Node.js Express JWT Authentication and Authorization application with MySQL/PostgreSQL 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:
– Node.js JWT Authentication & Authorization with MySQL example
– Node.js JWT Authentication & Authorization with PostgreSQL example
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.js");
...
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 Sequelize 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 MySQL/PostgreSQL databaseverifyExpiration
: compareexpiryDate
with current Date time to check the expiration
models/refreshToken.model.js
const config = require("../config/auth.config");
const { v4: uuidv4 } = require("uuid");
module.exports = (sequelize, Sequelize) => {
const RefreshToken = sequelize.define("refreshToken", {
token: {
type: Sequelize.STRING,
},
expiryDate: {
type: Sequelize.DATE,
},
});
RefreshToken.createToken = async function (user) {
let expiredAt = new Date();
expiredAt.setSeconds(expiredAt.getSeconds() + config.jwtRefreshExpiration);
let _token = uuidv4();
let refreshToken = await this.create({
token: _token,
userId: user.id,
expiryDate: expiredAt.getTime(),
});
return refreshToken.token;
};
RefreshToken.verifyExpiration = (token) => {
return token.expiryDate.getTime() < new Date().getTime();
};
return RefreshToken;
};
Don’t forget to use belongsTo()
and hasOne()
for configure association with User
model.
Then export RefreshToken
model in models/index.js:
const config = require("../config/db.config.js");
const Sequelize = require("sequelize");
const sequelize = new Sequelize( ... );
const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.user = require("../models/user.model.js")(sequelize, Sequelize);
db.role = require("../models/role.model.js")(sequelize, Sequelize);
db.refreshToken = require("../models/refreshToken.model.js")(sequelize, Sequelize);
db.role.belongsToMany(db.user, {
through: "user_roles",
foreignKey: "roleId",
otherKey: "userId"
});
db.user.belongsToMany(db.role, {
through: "user_roles",
foreignKey: "userId",
otherKey: "roleId"
});
db.refreshToken.belongsTo(db.user, {
foreignKey: 'userId', targetKey: 'id'
});
db.user.hasOne(db.refreshToken, {
foreignKey: 'userId', targetKey: 'id'
});
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 db = require("../models");
const config = require("../config/auth.config");
const { user: User, role: Role, refreshToken: RefreshToken } = db;
const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
...
exports.signin = (req, res) => {
User.findOne({
where: {
username: req.body.username
}
})
.then(async (user) => {
if (!user) {
return res.status(404).send({ message: "User Not found." });
}
const passwordIsValid = bcrypt.compareSync(
req.body.password,
user.password
);
if (!passwordIsValid) {
return res.status(401).send({
accessToken: null,
message: "Invalid Password!"
});
}
const token = jwt.sign({ id: user.id }, config.secret, {
expiresIn: config.jwtExpiration
});
let refreshToken = await RefreshToken.createToken(user);
let authorities = [];
user.getRoles().then(roles => {
for (let i = 0; i < roles.length; i++) {
authorities.push("ROLE_" + roles[i].name.toUpperCase());
}
res.status(200).send({
id: user.id,
username: user.username,
email: user.email,
roles: authorities,
accessToken: token,
refreshToken: refreshToken,
});
});
})
.catch(err => {
res.status(500).send({ message: err.message });
});
};
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({ where: { token: requestToken } });
console.log(refreshToken)
if (!refreshToken) {
res.status(403).json({ message: "Refresh token is not in database!" });
return;
}
if (RefreshToken.verifyExpiration(refreshToken)) {
RefreshToken.destroy({ where: { id: refreshToken.id } });
res.status(403).json({
message: "Refresh token was expired. Please make a new signin request",
});
return;
}
const user = await refreshToken.getUser();
let newAccessToken = jwt.sign({ id: 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 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 Node.js Rest Api example using Express, Sequelize and MySQL or PostgreSQL. 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 JWT Authentication & Authorization with MySQL example
– Node.js JWT Authentication & Authorization with PostgreSQL example
If you want to use MongoDB instead, please visit:
JWT Refresh Token implementation in Node.js and MongoDB
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
- Sequelize Associations
Fullstack (JWT Authentication & Authorization example):
– Node.js Express + Vue.js
– Node.js Express + Angular 8
– Node.js Express + Angular 10
– Node.js Express + Angular 11
– Node.js Express + Angular 12
– Node.js Express + React
Deployment:
– Deploying/Hosting Node.js app on Heroku with MySQL database
– Dockerize Node.js Express and MySQL example – Docker Compose
Source Code
You can find the complete source code for this tutorial on Github.
Hello, I have one question. Should I create one more folder like repository for interacting with database. I think I should separate business logic and database interaction folder like controller folder
ive got following response and i dont know how to deal with it any ideas?
“message”: “(conn=11890, no: 1292, SQLState: 22007) Incorrect datetime value: ‘Invalid date’ for column `karolpfr_testingDB`.`refreshTokens`.`expiryDate` at row 1\nsql: INSERT INTO `refreshTokens` (`id`,`token`,`expiryDate`,`createdAt`,`updatedAt`,`userId`) VALUES (DEFAULT,?,?,?,?,?); – parameters:[’22e207ce-bb9f-4cad-b815-f1635ac3e812′,’Invalid date’,’2022-02-21 16:38:42.311′,’2022-02-21 16:38:42.311′,1]”
I’m having issues with an error for refreshToken.getUser().
Where is this function from? I’ve searched the repo and can’t find it.
I thank you for the posting. In the method that creates the refresh token, where is this.create is defined?
i love this tutorial nice explanation and everything is clear.
One question though i see that you have in your auth.controller.js
const user = await refreshToken.getUser();
let newAccessToken = jwt.sign({ id: user.id }, config.secret, {
expiresIn: config.jwtExpiration,
});
Where is the getUser() function coming from?
Great job! This is an awesome support for developers; It saves me too much time!
Can you show us how you implement a revoke system? I used a node persistent cache to check if the accessToken is revoked, but I would love to see how you manage it.
Thanks!
Anyone got this issue
/app/controllers/auth.controller.js:70
let refreshToken = await RefreshToken.createToken(user);
^^^^^
SyntaxError: await is only valid in async function
Hi, you forget to put
async
:Hi,
I cannot find the mysql table structure for the refreshToken in the article or the github link. Is it in an other article ?
Hi, we define the model in models/refreshToken.model.js.
https://github.com/bezkoder/jwt-refresh-token-node-js/blob/master/app/models/refreshToken.model.js
Thank you for the quick reply. Sorry, I meant the datatypes. I just realized Sequelize.STRING = varchar(255). and Sequelize.DATE = DATETIME. Are id and userId INT(10) ? I assume id also has auto increment?
Hi! Thank you very much. Your tutorials are very awesome.