├── .gitignore ├── README.md ├── app ├── config │ ├── auth.config.js │ └── db.config.js ├── controllers │ ├── auth.controller.js │ └── user.controller.js ├── middlewares │ ├── authJwt.js │ ├── index.js │ └── verifySignUp.js ├── models │ ├── index.js │ ├── refreshToken.model.js │ ├── role.model.js │ └── user.model.js └── routes │ ├── auth.routes.js │ └── user.routes.js ├── jwt-refresh-token-node-js-example-flow.png ├── jwt-token-authentication-node-js-example-flow.png ├── package.json └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js JWT Refresh Token with MongoDB example 2 | JWT Refresh Token Implementation with Node.js Express and MongoDB. You can know how to expire the JWT, then renew the Access Token with Refresh Token. 3 | 4 | For instruction, please visit: 5 | > [Node.js JWT Refresh Token with MongoDB example](https://bezkoder.com/jwt-refresh-token-node-js-mongodb/) 6 | 7 | The code in this post bases on previous article that you need to read first: 8 | > [Node.js + MongoDB: User Authentication & Authorization with JWT](https://bezkoder.com/node-js-mongodb-auth-jwt/) 9 | 10 | ## User Registration, User Login and Authorization process. 11 | 12 | The diagram shows flow of how we implement User Registration, User Login and Authorization process. 13 | 14 | ![jwt-token-authentication-node-js-example-flow](jwt-token-authentication-node-js-example-flow.png) 15 | 16 | And this is for Refresh Token: 17 | 18 | ![jwt-refresh-token-node-js-example-flow](jwt-refresh-token-node-js-example-flow.png) 19 | 20 | ## More Practice: 21 | > [Node.js, Express & MongoDb: Build a CRUD Rest Api example](https://bezkoder.com/node-express-mongodb-crud-rest-api/) 22 | 23 | > [Server side Pagination in Node.js with MongoDB and Mongoose](https://bezkoder.com/node-js-mongodb-pagination/) 24 | 25 | Associations: 26 | > [MongoDB One-to-One relationship tutorial with Mongoose examples](https://bezkoder.com/mongoose-one-to-one-relationship-example/) 27 | 28 | > [MongoDB One-to-Many Relationship tutorial with Mongoose examples](https://bezkoder.com/mongoose-one-to-many-relationship/) 29 | 30 | > [MongoDB Many-to-Many Relationship with Mongoose examples](https://bezkoder.com/mongodb-many-to-many-mongoose/) 31 | 32 | Fullstack: 33 | > [Vue.js + Node.js + Express + MySQL example](https://bezkoder.com/vue-js-node-js-express-mysql-crud-example/) 34 | 35 | > [Vue.js + Node.js + Express + PostgreSQL example](https://bezkoder.com/vue-node-express-postgresql/) 36 | 37 | > [Vue.js + Node.js + Express + MongoDB example](https://bezkoder.com/vue-node-express-mongodb-mevn-crud/) 38 | 39 | > [Angular 8 + Node.js + Express + MySQL example](https://bezkoder.com/angular-node-express-mysql/) 40 | 41 | > [Angular 8 + Node.js + Express + PostgreSQL example](https://bezkoder.com/angular-node-express-postgresql/) 42 | 43 | > [Angular 8 + Node.js + Express + MongoDB example](https://bezkoder.com/angular-mongodb-node-express/) 44 | 45 | > [Angular 10 + Node.js + Express + MySQL example](https://bezkoder.com/angular-10-node-js-express-mysql/) 46 | 47 | > [Angular 10 + Node.js + Express + PostgreSQL example](https://bezkoder.com/angular-10-node-express-postgresql/) 48 | 49 | > [Angular 10 + Node.js + Express + MongoDB example](https://bezkoder.com/angular-10-mongodb-node-express/) 50 | 51 | > [Angular 11 + Node.js Express + MySQL example](https://bezkoder.com/angular-11-node-js-express-mysql/) 52 | 53 | > [Angular 11 + Node.js + Express + PostgreSQL example](https://bezkoder.com/angular-11-node-js-express-postgresql/) 54 | 55 | > [Angular 11 + Node.js + Express + MongoDB example](https://bezkoder.com/angular-11-mongodb-node-js-express/) 56 | 57 | > [React + Node.js + Express + MySQL example](https://bezkoder.com/react-node-express-mysql/) 58 | 59 | > [React + Node.js + Express + PostgreSQL example](https://bezkoder.com/react-node-express-postgresql/) 60 | 61 | > [React + Node.js + Express + MongoDB example](https://bezkoder.com/react-node-express-mongodb-mern-stack/) 62 | 63 | Integration (run back-end & front-end on same server/port) 64 | > [Integrate React with Node.js Restful Services](https://bezkoder.com/integrate-react-express-same-server-port/) 65 | 66 | > [Integrate Angular with Node.js Restful Services](https://bezkoder.com/integrate-angular-10-node-js/) 67 | 68 | > [Integrate Vue with Node.js Restful Services](https://bezkoder.com/serve-vue-app-express/) 69 | 70 | ## Project setup 71 | ``` 72 | npm install 73 | ``` 74 | 75 | ### Run 76 | ``` 77 | node server.js 78 | ``` 79 | -------------------------------------------------------------------------------- /app/config/auth.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | secret: "bezkoder-secret-key", 3 | // jwtExpiration: 3600, // 1 hour 4 | // jwtRefreshExpiration: 86400, // 24 hours 5 | 6 | /* for test */ 7 | jwtExpiration: 60, // 1 minute 8 | jwtRefreshExpiration: 120, // 2 minutes 9 | }; 10 | -------------------------------------------------------------------------------- /app/config/db.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | HOST: "localhost", 3 | PORT: 27017, 4 | DB: "bezkoder_db" 5 | }; -------------------------------------------------------------------------------- /app/controllers/auth.controller.js: -------------------------------------------------------------------------------- 1 | const config = require("../config/auth.config"); 2 | const db = require("../models"); 3 | const { user: User, role: Role, refreshToken: RefreshToken } = db; 4 | 5 | const jwt = require("jsonwebtoken"); 6 | const bcrypt = require("bcryptjs"); 7 | 8 | exports.signup = (req, res) => { 9 | const user = new User({ 10 | username: req.body.username, 11 | email: req.body.email, 12 | password: bcrypt.hashSync(req.body.password, 8), 13 | }); 14 | 15 | user.save((err, user) => { 16 | if (err) { 17 | res.status(500).send({ message: err }); 18 | return; 19 | } 20 | 21 | if (req.body.roles) { 22 | Role.find( 23 | { 24 | name: { $in: req.body.roles }, 25 | }, 26 | (err, roles) => { 27 | if (err) { 28 | res.status(500).send({ message: err }); 29 | return; 30 | } 31 | 32 | user.roles = roles.map((role) => role._id); 33 | user.save((err) => { 34 | if (err) { 35 | res.status(500).send({ message: err }); 36 | return; 37 | } 38 | 39 | res.send({ message: "User was registered successfully!" }); 40 | }); 41 | } 42 | ); 43 | } else { 44 | Role.findOne({ name: "user" }, (err, role) => { 45 | if (err) { 46 | res.status(500).send({ message: err }); 47 | return; 48 | } 49 | 50 | user.roles = [role._id]; 51 | user.save((err) => { 52 | if (err) { 53 | res.status(500).send({ message: err }); 54 | return; 55 | } 56 | 57 | res.send({ message: "User was registered successfully!" }); 58 | }); 59 | }); 60 | } 61 | }); 62 | }; 63 | 64 | exports.signin = (req, res) => { 65 | User.findOne({ 66 | username: req.body.username, 67 | }) 68 | .populate("roles", "-__v") 69 | .exec(async (err, user) => { 70 | if (err) { 71 | res.status(500).send({ message: err }); 72 | return; 73 | } 74 | 75 | if (!user) { 76 | return res.status(404).send({ message: "User Not found." }); 77 | } 78 | 79 | let passwordIsValid = bcrypt.compareSync( 80 | req.body.password, 81 | user.password 82 | ); 83 | 84 | if (!passwordIsValid) { 85 | return res.status(401).send({ 86 | accessToken: null, 87 | message: "Invalid Password!", 88 | }); 89 | } 90 | 91 | let token = jwt.sign({ id: user.id }, config.secret, { 92 | expiresIn: config.jwtExpiration, 93 | }); 94 | 95 | let refreshToken = await RefreshToken.createToken(user); 96 | 97 | let authorities = []; 98 | 99 | for (let i = 0; i < user.roles.length; i++) { 100 | authorities.push("ROLE_" + user.roles[i].name.toUpperCase()); 101 | } 102 | res.status(200).send({ 103 | id: user._id, 104 | username: user.username, 105 | email: user.email, 106 | roles: authorities, 107 | accessToken: token, 108 | refreshToken: refreshToken, 109 | }); 110 | }); 111 | }; 112 | 113 | exports.refreshToken = async (req, res) => { 114 | const { refreshToken: requestToken } = req.body; 115 | 116 | if (requestToken == null) { 117 | return res.status(403).json({ message: "Refresh Token is required!" }); 118 | } 119 | 120 | try { 121 | let refreshToken = await RefreshToken.findOne({ token: requestToken }); 122 | 123 | if (!refreshToken) { 124 | res.status(403).json({ message: "Refresh token is not in database!" }); 125 | return; 126 | } 127 | 128 | if (RefreshToken.verifyExpiration(refreshToken)) { 129 | RefreshToken.findByIdAndRemove(refreshToken._id, { useFindAndModify: false }).exec(); 130 | 131 | res.status(403).json({ 132 | message: "Refresh token was expired. Please make a new signin request", 133 | }); 134 | return; 135 | } 136 | 137 | let newAccessToken = jwt.sign({ id: refreshToken.user._id }, config.secret, { 138 | expiresIn: config.jwtExpiration, 139 | }); 140 | 141 | return res.status(200).json({ 142 | accessToken: newAccessToken, 143 | refreshToken: refreshToken.token, 144 | }); 145 | } catch (err) { 146 | return res.status(500).send({ message: err }); 147 | } 148 | }; 149 | -------------------------------------------------------------------------------- /app/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | exports.allAccess = (req, res) => { 2 | res.status(200).send("Public Content."); 3 | }; 4 | 5 | exports.userBoard = (req, res) => { 6 | res.status(200).send("User Content."); 7 | }; 8 | 9 | exports.adminBoard = (req, res) => { 10 | res.status(200).send("Admin Content."); 11 | }; 12 | 13 | exports.moderatorBoard = (req, res) => { 14 | res.status(200).send("Moderator Content."); 15 | }; 16 | -------------------------------------------------------------------------------- /app/middlewares/authJwt.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const config = require("../config/auth.config"); 3 | const db = require("../models"); 4 | const User = db.user; 5 | const Role = db.role; 6 | 7 | const { TokenExpiredError } = jwt; 8 | 9 | const catchError = (err, res) => { 10 | if (err instanceof TokenExpiredError) { 11 | return res.status(401).send({ message: "Unauthorized! Access Token was expired!" }); 12 | } 13 | 14 | return res.sendStatus(401).send({ message: "Unauthorized!" }); 15 | } 16 | 17 | const verifyToken = (req, res, next) => { 18 | let token = req.headers["x-access-token"]; 19 | 20 | if (!token) { 21 | return res.status(403).send({ message: "No token provided!" }); 22 | } 23 | 24 | jwt.verify(token, config.secret, (err, decoded) => { 25 | if (err) { 26 | return catchError(err, res); 27 | } 28 | req.userId = decoded.id; 29 | next(); 30 | }); 31 | }; 32 | 33 | const isAdmin = (req, res, next) => { 34 | User.findById(req.userId).exec((err, user) => { 35 | if (err) { 36 | res.status(500).send({ message: err }); 37 | return; 38 | } 39 | 40 | Role.find( 41 | { 42 | _id: { $in: user.roles } 43 | }, 44 | (err, roles) => { 45 | if (err) { 46 | res.status(500).send({ message: err }); 47 | return; 48 | } 49 | 50 | for (let i = 0; i < roles.length; i++) { 51 | if (roles[i].name === "admin") { 52 | next(); 53 | return; 54 | } 55 | } 56 | 57 | res.status(403).send({ message: "Require Admin Role!" }); 58 | return; 59 | } 60 | ); 61 | }); 62 | }; 63 | 64 | const isModerator = (req, res, next) => { 65 | User.findById(req.userId).exec((err, user) => { 66 | if (err) { 67 | res.status(500).send({ message: err }); 68 | return; 69 | } 70 | 71 | Role.find( 72 | { 73 | _id: { $in: user.roles } 74 | }, 75 | (err, roles) => { 76 | if (err) { 77 | res.status(500).send({ message: err }); 78 | return; 79 | } 80 | 81 | for (let i = 0; i < roles.length; i++) { 82 | if (roles[i].name === "moderator") { 83 | next(); 84 | return; 85 | } 86 | } 87 | 88 | res.status(403).send({ message: "Require Moderator Role!" }); 89 | return; 90 | } 91 | ); 92 | }); 93 | }; 94 | 95 | const authJwt = { 96 | verifyToken, 97 | isAdmin, 98 | isModerator 99 | }; 100 | module.exports = authJwt; 101 | -------------------------------------------------------------------------------- /app/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const authJwt = require("./authJwt"); 2 | const verifySignUp = require("./verifySignUp"); 3 | 4 | module.exports = { 5 | authJwt, 6 | verifySignUp 7 | }; 8 | -------------------------------------------------------------------------------- /app/middlewares/verifySignUp.js: -------------------------------------------------------------------------------- 1 | const db = require("../models"); 2 | const ROLES = db.ROLES; 3 | const User = db.user; 4 | 5 | checkDuplicateUsernameOrEmail = (req, res, next) => { 6 | // Username 7 | User.findOne({ 8 | username: req.body.username 9 | }).exec((err, user) => { 10 | if (err) { 11 | res.status(500).send({ message: err }); 12 | return; 13 | } 14 | 15 | if (user) { 16 | res.status(400).send({ message: "Failed! Username is already in use!" }); 17 | return; 18 | } 19 | 20 | // Email 21 | User.findOne({ 22 | email: req.body.email 23 | }).exec((err, user) => { 24 | if (err) { 25 | res.status(500).send({ message: err }); 26 | return; 27 | } 28 | 29 | if (user) { 30 | res.status(400).send({ message: "Failed! Email is already in use!" }); 31 | return; 32 | } 33 | 34 | next(); 35 | }); 36 | }); 37 | }; 38 | 39 | checkRolesExisted = (req, res, next) => { 40 | if (req.body.roles) { 41 | for (let i = 0; i < req.body.roles.length; i++) { 42 | if (!ROLES.includes(req.body.roles[i])) { 43 | res.status(400).send({ 44 | message: `Failed! Role ${req.body.roles[i]} does not exist!` 45 | }); 46 | return; 47 | } 48 | } 49 | } 50 | 51 | next(); 52 | }; 53 | 54 | const verifySignUp = { 55 | checkDuplicateUsernameOrEmail, 56 | checkRolesExisted 57 | }; 58 | 59 | module.exports = verifySignUp; 60 | -------------------------------------------------------------------------------- /app/models/index.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | mongoose.Promise = global.Promise; 3 | 4 | const db = {}; 5 | 6 | db.mongoose = mongoose; 7 | 8 | db.user = require("./user.model"); 9 | db.role = require("./role.model"); 10 | db.refreshToken = require("./refreshToken.model"); 11 | 12 | db.ROLES = ["user", "admin", "moderator"]; 13 | 14 | module.exports = db; -------------------------------------------------------------------------------- /app/models/refreshToken.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const config = require("../config/auth.config"); 3 | const { v4: uuidv4 } = require('uuid'); 4 | 5 | const RefreshTokenSchema = new mongoose.Schema({ 6 | token: String, 7 | user: { 8 | type: mongoose.Schema.Types.ObjectId, 9 | ref: "User", 10 | }, 11 | expiryDate: Date, 12 | }); 13 | 14 | RefreshTokenSchema.statics.createToken = async function (user) { 15 | let expiredAt = new Date(); 16 | 17 | expiredAt.setSeconds( 18 | expiredAt.getSeconds() + config.jwtRefreshExpiration 19 | ); 20 | 21 | let _token = uuidv4(); 22 | 23 | let _object = new this({ 24 | token: _token, 25 | user: user._id, 26 | expiryDate: expiredAt.getTime(), 27 | }); 28 | 29 | console.log(_object); 30 | 31 | let refreshToken = await _object.save(); 32 | 33 | return refreshToken.token; 34 | }; 35 | 36 | RefreshTokenSchema.statics.verifyExpiration = (token) => { 37 | return token.expiryDate.getTime() < new Date().getTime(); 38 | } 39 | 40 | const RefreshToken = mongoose.model("RefreshToken", RefreshTokenSchema); 41 | 42 | module.exports = RefreshToken; 43 | -------------------------------------------------------------------------------- /app/models/role.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const Role = mongoose.model( 4 | "Role", 5 | new mongoose.Schema({ 6 | name: String 7 | }) 8 | ); 9 | 10 | module.exports = Role; 11 | -------------------------------------------------------------------------------- /app/models/user.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const User = mongoose.model( 4 | "User", 5 | new mongoose.Schema({ 6 | username: String, 7 | email: String, 8 | password: String, 9 | roles: [ 10 | { 11 | type: mongoose.Schema.Types.ObjectId, 12 | ref: "Role" 13 | } 14 | ] 15 | }) 16 | ); 17 | 18 | module.exports = User; 19 | -------------------------------------------------------------------------------- /app/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | const { verifySignUp } = require("../middlewares"); 2 | const controller = require("../controllers/auth.controller"); 3 | 4 | module.exports = function(app) { 5 | app.use(function(req, res, next) { 6 | res.header( 7 | "Access-Control-Allow-Headers", 8 | "x-access-token, Origin, Content-Type, Accept" 9 | ); 10 | next(); 11 | }); 12 | 13 | app.post( 14 | "/api/auth/signup", 15 | [ 16 | verifySignUp.checkDuplicateUsernameOrEmail, 17 | verifySignUp.checkRolesExisted 18 | ], 19 | controller.signup 20 | ); 21 | 22 | app.post("/api/auth/signin", controller.signin); 23 | 24 | app.post("/api/auth/refreshtoken", controller.refreshToken); 25 | }; 26 | -------------------------------------------------------------------------------- /app/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | const { authJwt } = require("../middlewares"); 2 | const controller = require("../controllers/user.controller"); 3 | 4 | module.exports = function(app) { 5 | app.use(function(req, res, next) { 6 | res.header( 7 | "Access-Control-Allow-Headers", 8 | "x-access-token, Origin, Content-Type, Accept" 9 | ); 10 | next(); 11 | }); 12 | 13 | app.get("/api/test/all", controller.allAccess); 14 | 15 | app.get("/api/test/user", [authJwt.verifyToken], controller.userBoard); 16 | 17 | app.get( 18 | "/api/test/mod", 19 | [authJwt.verifyToken, authJwt.isModerator], 20 | controller.moderatorBoard 21 | ); 22 | 23 | app.get( 24 | "/api/test/admin", 25 | [authJwt.verifyToken, authJwt.isAdmin], 26 | controller.adminBoard 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /jwt-refresh-token-node-js-example-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bezkoder/jwt-refresh-token-node-js-mongodb/f77d593c2429b531ed022738f676fe0a9ca7861b/jwt-refresh-token-node-js-example-flow.png -------------------------------------------------------------------------------- /jwt-token-authentication-node-js-example-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bezkoder/jwt-refresh-token-node-js-mongodb/f77d593c2429b531ed022738f676fe0a9ca7861b/jwt-token-authentication-node-js-example-flow.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-js-jwt-refresh-token-mongodb", 3 | "version": "1.0.0", 4 | "description": "Node.js JWT Refresh Token with MongoDB", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "node.js", 11 | "express", 12 | "jwt", 13 | "refresh token", 14 | "authentication", 15 | "authorization", 16 | "mongodb" 17 | ], 18 | "author": "bezkoder", 19 | "license": "ISC", 20 | "dependencies": { 21 | "bcryptjs": "^2.4.3", 22 | "body-parser": "^1.19.0", 23 | "cors": "^2.8.5", 24 | "express": "^4.17.1", 25 | "jsonwebtoken": "^8.5.1", 26 | "mongoose": "^5.12.10", 27 | "uuid": "^8.3.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require("cors"); 3 | const dbConfig = require("./app/config/db.config"); 4 | 5 | const app = express(); 6 | 7 | let corsOptions = { 8 | origin: "http://localhost:8081" 9 | }; 10 | 11 | app.use(cors(corsOptions)); 12 | 13 | // parse requests of content-type - application/json 14 | app.use(express.json()); 15 | 16 | // parse requests of content-type - application/x-www-form-urlencoded 17 | app.use(express.urlencoded({ extended: true })); 18 | 19 | const db = require("./app/models"); 20 | const Role = db.role; 21 | 22 | db.mongoose 23 | .connect(`mongodb://${dbConfig.HOST}:${dbConfig.PORT}/${dbConfig.DB}`, { 24 | useNewUrlParser: true, 25 | useUnifiedTopology: true 26 | }) 27 | .then(() => { 28 | console.log("Successfully connect to MongoDB."); 29 | initial(); 30 | }) 31 | .catch(err => { 32 | console.error("Connection error", err); 33 | process.exit(); 34 | }); 35 | 36 | // simple route 37 | app.get("/", (req, res) => { 38 | res.json({ message: "Welcome to bezkoder application." }); 39 | }); 40 | 41 | // routes 42 | require("./app/routes/auth.routes")(app); 43 | require("./app/routes/user.routes")(app); 44 | 45 | // set port, listen for requests 46 | const PORT = process.env.PORT || 8080; 47 | app.listen(PORT, () => { 48 | console.log(`Server is running on port ${PORT}.`); 49 | }); 50 | 51 | function initial() { 52 | Role.estimatedDocumentCount((err, count) => { 53 | if (!err && count === 0) { 54 | new Role({ 55 | name: "user" 56 | }).save(err => { 57 | if (err) { 58 | console.log("error", err); 59 | } 60 | 61 | console.log("added 'user' to roles collection"); 62 | }); 63 | 64 | new Role({ 65 | name: "moderator" 66 | }).save(err => { 67 | if (err) { 68 | console.log("error", err); 69 | } 70 | 71 | console.log("added 'moderator' to roles collection"); 72 | }); 73 | 74 | new Role({ 75 | name: "admin" 76 | }).save(err => { 77 | if (err) { 78 | console.log("error", err); 79 | } 80 | 81 | console.log("added 'admin' to roles collection"); 82 | }); 83 | } 84 | }); 85 | } 86 | --------------------------------------------------------------------------------