JWT Refresh Token implementation in Node.js example

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

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


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:

MethodsUrlsActions
POST/api/auth/signupsignup new account
POST/api/auth/signinlogin an account
GET/api/test/allretrieve public content
GET/api/test/useraccess User’s content
GET/api/test/modaccess Moderator’s content
GET/api/test/adminaccess 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.

jwt-refresh-token-node-js-implementation-example-signin

– Access resource successfully with accessToken.

jwt-refresh-token-node-js-implementation-example-access-resource

– When the accessToken is expired, user cannot use it anymore.

jwt-refresh-token-node-js-implementation-example-expire-token

– Send /refreshtoken request, return response with new accessToken.

jwt-refresh-token-node-js-implementation-example-send-token-refresh-request

– Access resource successfully with new accessToken.

jwt-refresh-token-node-js-implementation-example-new-token-access-resource

– Send an expired Refresh Token.

jwt-refresh-token-node-js-implementation-example-expire-refresh-token

– Send an inexistent Refresh Token.

jwt-refresh-token-node-js-implementation-example-token-not-exist-database

Flow for JWT Refresh Token implementation

The diagram shows flow of how we implement Authentication process with Access Token and Refresh Token.

jwt-refresh-token-node-js-example-flow

– 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: use uuid library for creating a random token and save new object into MySQL/PostgreSQL database
  • verifyExpiration: compare expiryDate with current Date time to check the expiration
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 using RefreshToken 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 of RefreshToken object as parameter to generate new Access Token using jsonwebtoken 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

Happy learning! See you again.

Further Reading

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

Source Code

You can find the complete source code for this tutorial on Github.

Leave a Reply

Your email address will not be published. Required fields are marked *