The safest way to use JWTs

Subscribe to my newsletter and never miss my upcoming articles

JWTs are probably one of the most used ways to authenticate users. But most people use them in the wrong way which has serious security issues.

JWT tokens are incredibly sensitive to information. Even a single change in the payload can change the hash/token structure entirely.

Structure of JWT:

  1. Header

  2. Payload

  3. Signature

  4. Header: The header is used to specify the algorithm to be used to generate JWTs.

  5. Payload: Payload is the data transmitted. Usually encoded in base64.

  6. Signature: Signature is used to verify if the data transmitted from the other side has not tampered with.

Putting it all together, the format of the JWT looks like: XXXX.YYYY.ZZZZ. (Header.Payload.Signature)

How most people use it?

Most people don't understand how important the JWT token is. The procedure they follow is:

  1. When a User signs-in, they validate the user and sign a JWT token. This is sent to the client.

  2. Whenever authentication is needed, this token is sent as a header.

  3. This Token is stored in cookies (HTTP-cookies) inside the browser.

The security flaw in this approach is that, if any malicious user claims access to the token backend, then he can have access to protected content forever. This is the biggest flaw in this method.

So, What can be a valid solution?

One possible solution is to limit the time/validity of the token so that even if someone claims the token, he/she won’t have much time to make great use of it.

But, making the token-validity shorter means that users have to login every-time for a token to be issued. This is also a problem, we can’t ask the user to log in like every 10 minutes or so.

So, What can we do to solve the main issue as well as the previous issue?

One great way to deal with JWTs is to create 2 separate tokens:

  1. Access Token

  2. Refresh Token

  3. Access Token: This is the token used to authenticate requests. Access tokens have a short lifetime as discussed, 10 minute lifetime. When this token expires, the refresh token comes into play.

  4. Refresh Token: When Access-Token expires, a refresh token is used to create another access-token. So, another token of 10 minute lifetime is created. The user doesn’t know this. Everything is taken care of on the backend. The Lifetime of Refresh Token is greater than Access-Token. Most people tend to go up to 1 month with expiration.

So, this solves the issue. But, how is this implemented?


The flow goes like this:

  1. The user hits some protected route, which needs the user to be logged-in.

  2. We need to check the access token of the user, so both access and refresh tokens are sent to the server.

  3. If the access token is not valid, the refresh token is used to generate a new access token.

  4. For that, the validity of the refresh-token is checked. If not valid, the User is asked to log in again.

  5. Else, a new access token is generated and is used for any authenticated until this access-token expires.

// Function used to log the user in.
function login() {
    let accessToken = jwt.sign(user, "access", { expiresIn: "10m" }); // 10 Min-Expiry.
    let refreshToken = jwt.sign(user, "refresh", { expiresIn: "7d" }); // 7 Day-Expiry.

    return { accessToken, refreshToken };
}

// Function to verify if the user is logged-in or not.
function verifyAuthentication() {
    const { token } = req.headers; // Access-Token sent via Header.
    jwt.verify(token, "access", (err, user) => {
        if (!err) {
            req.user = user;
        } else {
            return { message: "User not authenticated" };
        }
    });
}

// Function to renew refresh-token.
function renewRefreshToken() {
    const refreshToken = req.body.token;
    if (!refreshToken) {
        return { message: "User not authenticated" };
    }

    jwt.verify(refreshToken, "refresh", (err, user) => {
        if (!err) {
            // Valid Refresh-Token
            const accessToken = jwt.sign({ username: user.name }, "access", {
                expiresIn: "20s"
            });
            return { accessToken };
        } else {
            // Refresh-Token not valid, user must login.
            return { message: "User not authenticated" };
        }
    });
}

Further, we can maintain an array of refresh-tokens, then check the validity of the refresh-token.

So, this is not the only method to use JWTs; but one of the safest ways to do so.

Fact: JWTs can be used to exchange information apart from authenticating users!

JWT Official Site

No Comments Yet