Access And Refresh Tokens Explained Simply!

Archit ChhajedArchit Chhajed
7 min read

So, you might have heard if Access and Refresh tokens of you've ever studied about web-authentication and user authorization. But many people get confused as to what exactly are they? Why do we need them? Why can't we have only 1 token and authorize our user throught that? Why we need 2 different tokens.

Well, today I Archit Chhajed, an aspiring web developer will aim to resolve this question for you in language that even a 5 year old can understand. We won't use any harsh technical jargons and keep it simple. Also the code example are provided for readers with more indepth knowledge, however it's not expected that everyone who reads the code will understand it in the first go, but it helps increase clarity.

Why do we need access and refresh tokens?

A website needs tokens to verify weather the request it recieves is from a valid user. It would be quite annoying if everytime you wanted to comment on a video or like a video, you had to login and enter your crendentials (password) everytime! Hence we need a facillity to allow the user to remain logged in and also allow the user to re-login once his or her session expires. This facillity is easily achieved through access and refresh tokens stored in cookies!

What Are Refresh Tokens and How to Use Them Securely

Definition

"Tokens are pieces of data that carry just enough information to facilitate the process of determining a user's identity or authorizing a user to perform an action."
So tokens are basically pieces of information that are usually stored in a web browser's cookies.

Access Tokens

Access Tokens: Access token are tokens that allow the server to verify that a request that has come has come from a valid user present in the database. Access tokens are not stored in the database of a website. They are only present with the user.

Access tokens containes an ENCRYPTED FORM of user login credentials which are sent and DECRYPTED on the server to verify user credentials safely and securely. The decryption key is present on the server securely. This is done through various library like JWT in javascript.

Below is a code example:

userSchema.methods.generateAccessToken = async function()
{
    return await jwt.sign(
        {
            _id: this._id, //info to be encrypted
            email: this.email,
            username: this.username,
            fullName: this.fullName,

        },
        process.env.ACCESS_TOKEN_SECRET, //encryption key
        {
            expiresIn: process.env.ACCESS_TOKEN_EXPIRY
        }
    );
}

Whenever the user logs in, the user is provided with an access token, so whenever the user requests a resource or accesses a content, they only require the correct access token.

This access token is an encrypted form of all details of the user like "name, password, email etc" and when the server recieves this token, it decodes this token to get the "name, password, email" and verify it in it's database. If upon decoding the information recieved it correct then, the user is granted access. If not, then invalid access token error is given.

Below is a code example of a middleware for checking correct "access token" through a function called verifyJWT()

export const verifyJWT = asyncHandler(async (req, res, next) => {
  try {
    const token =
      req.cookies?.accessToken ||
      req.header("Authorization")?.replace("Bearer ", ""); //replace "Bearer <access token>" with just "<access token>"
    console.log("TOKEN: ", token);
    console.log("req: ", req.body);
    if (!token) {
      throw new ApiError(401, "Unauthorized request 111");
    }
    const decodedTokenInfo = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET);
    const user = await User.findById(decodedTokenInfo?._id).select(
      "-password -refreshToken"
    );

    if (!user) {
      // NEXT_VIDEO: discuss about frontend
      throw new ApiError(401, "Invalid Access Token");
    }

    // If user is verified, then we add something to the request object
    req.user = user;
    next();
  } catch (error) {
    throw new ApiError(401, error?.message || "Invalid Access Token");
  }
});

However this is not really secure, since if someone who has this access token, can pretend to be the user and log in! So in order to increase security, this access token, has a short expiry time, for example, 30 minutes or 1 hour, that is after a login, the user can continue to enjoy all resources of a website for 1 hour, after one hour, the access token would be DELETED from the cookies! And the user would have to log in again!

But this doesn't happen in real life does it? We don't login every 15 to 30 minutes on youtube, instagram, google etc! So how does it work? Well, here the concept of refresh token comes in!

Refresh Tokens

Refresh Token: Refresh tokens like access tokens also allow the user to be verified by the server, but moreover it's main purpose is to REGENERATE THE ACCESS TOKEN, without the user having to enter it's credentials again!

So whenever the access token is expired, the user's request which contains the refresh token as well is matched with the refresh token present in the database. If it matches then a new access token is generated, which is then granted to the user!

It's kind of like a pass to generate a new ticket.

Let's see through a code example how a refresh token is generated:

userSchema.methods.generateRefreshToken = async function(){
    console.log("ENTERED");
    console.log(process.env.REFRESH_TOKEN_SECRET);
    // console.log(REFRESH_TOKEN_EXPIRY)
    console.log(this._id);
    return await jwt.sign(
        {
            _id: this._id //info to be encrypted
        },
        process.env.REFRESH_TOKEN_SECRET, //encryption key
        {
            expiresIn: process.env.REFRESH_TOKEN_EXPIRY //lifespan:  10 days
        }
    );
}

Refresh tokens like access token's also have a lifespan, this lifespan is though much LONGER than that of the access token, generally 10 to 20 days. Upon expiry of the refresh token, the refresh token from cookies would be deleted and hence, it would not match the refresh token in the database and the user would be asked to enter the new credentials again.

If an old refresh token is possesed by some user, and it would not match the new refresh token in the database and hence the user would have to provide the login credentials again to regenerate the refresh token.

Here's a code example of how a refresh token is used to verify the user and generate a new access token:

const refreshAccessToken = asyncHandler(async (req, res) => {
  const incomingRefreshToken =
    req.cookies.refreshToken || req.body.refreshToken;

  if (!incomingRefreshToken) {
    throw new ApiError(401, "Incoming Refresh Token does not exist");
  }

  try {
    const decodedInfo = jwt.verify(
      incomingRefreshToken,
      process.env.REFRESH_TOKEN_SECRET
    );

    const user = await User.findById(decodedInfo._id);

    if (!user) {
      throw new ApiError(
        402,
        "The incoming refresh token does not correspond to any user present in the database"
      );
    }

    // now checking if the user that the incoming refresh token corresponds, actually has that refresh token equal to the incoming refresh token or not, if not, then that means that the refresh token that user has sent (incomingRefreshToken) has expired, that is it the expired token can still be decoded and user._id can be extracted from the expired token but since it is expired it might not match the token in the database

    if (incomingRefreshToken !== user.refreshToken) {
      throw new ApiError(403, "User's refresh token is used or expired");
    }

    const options = {
      httpOnly: true,
      secure: true,
    };

    const { accessToken, newRefreshToken } =
      await generateAccessAndRefreshToken(user._id);

    return res
      .status(200)
      .cookie("accessToken", accessToken, options)
      .cookie("refreshToken", newRefreshToken, options)
      .json(
        new ApiResponse(
          200,
          {
            accessToken,
            refreshToken: newRefreshToken,
          },
          "Access Token Refreshed"
        )
      );
  } catch (error) {
    throw new ApiError(401, error?.msg || "Invalid Refresh Token");
  }
});

Analogy for understanding!

Imagine you're in a theme park, in order to enter it, you need a pass (access token) which you get when you buy the ticket at the ticket counter (login), for going on rides you show this pass (Access token) and you can enter it. However this access token is only valid for a day, after which you need to buy another ticket for the next day!

Refresh token however is like a season pass for the theme park, whenever your ticket expires, you simply go the counter, show this pass and you immediately get the new access token, so that you can go on the rides again.

Conclusion

In conclusion this method of authentication is extremely useful to ensure user security and prevention of unauthorized access by malicious people. Access and refresh tokens are common talk amongst web developers and newer advanced methods are constantly being built in the backend to furthur enhance user protection of data.

Hope this article was useful, if you liked it please make sure to leave a feedback :)

Jai Shri Krishna!

1
Subscribe to my newsletter

Read articles from Archit Chhajed directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Archit Chhajed
Archit Chhajed

Pre-final year student at NIT Kurukshetra ๐ŸŽ“ with a strong foundation in C, C++, Java, JavaScript, HTML, CSS, Node.js, and React ๐Ÿ’ป. Passionate about web development and deeply interested in AI and ML ๐Ÿค–. Eager to leverage my skills and knowledge to create innovative solutions and contribute to cutting-edge projects ๐Ÿš€.