Revoking JWTs: A Simple Guide

Revoking JWTs: A Simple Guide

Tip: Select any text in this article to create a note with your thoughts and insights!

JSON Web Tokens (JWTs) are awesome for authentication, but revoking them can be a bit tricky since they’re designed to be stateless. This means they don’t rely on server storage and are valid until they expire. So, how do you “cancel” a JWT when a user logs out or if a token is compromised? Don’t worry—I’ll walk you through the most practical ways to revoke JWTs in simple terms, with examples you can use.

Why Is Revoking JWTs Hard?

A JWT is like a concert ticket: once it’s issued, it’s valid until it expires (based on the exp claim). The server doesn’t keep a copy to “cancel” it—it just checks the signature and expiration when presented. This makes JWTs scalable but tricky to revoke. Here’s how you can handle it:

1. Short-Lived Tokens with Refresh Tokens

The easiest way is to make JWTs short-lived (e.g., 5–15 minutes) and use a refresh token for longer sessions. The refresh token is stored on the server and can be revoked, effectively stopping new JWTs from being issued.

How It Works

  1. Login: The server issues a short-lived JWT (access token) and a refresh token.
  2. Revocation: To revoke access, delete the refresh token from the server.
  3. Client: When the JWT expires, the client uses the refresh token to get a new one. If the refresh token is gone, access is denied.

Example Code

const jwt = require('jsonwebtoken');
const accessToken = jwt.sign({ userId: 1 }, 'accessSecret', { expiresIn: '15m' });
const refreshToken = jwt.sign({ userId: 1 }, 'refreshSecret', { expiresIn: '7d' });
// Store refreshToken in a database (e.g., Redis)
  

Revoke on Logout: Delete the refresh token from the database.

app.post('/refresh', async (req, res) => {
  const { refreshToken } = req.body;
  if (!await isValidRefreshToken(refreshToken)) {
    return res.status(401).json({ error: 'Invalid refresh token' });
  }
  try {
    const decoded = jwt.verify(refreshToken, 'refreshSecret');
    const newAccessToken = jwt.sign({ userId: decoded.userId }, 'accessSecret', { expiresIn: '15m' });
    res.json({ accessToken: newAccessToken });
  } catch (err) {
    res.status(401).json({ error: 'Invalid or expired refresh token' });
  }
});
  

Pros

  • Short-lived JWTs limit risk.
  • Refresh tokens are server-controlled, so revocation is easy.
  • Scales well with fast databases like Redis.

Cons

  • Needs server-side storage for refresh tokens.
  • Clients must handle token refreshes.

2. Token Blacklist

You can maintain a blacklist of revoked tokens. When a user logs out, add their JWT (or its unique ID) to the blacklist, and check it before accepting a token.

How It Works

  1. Logout: Add the JWT’s unique ID (jti) to a blacklist in a database.
  2. Verification: Check if the token is blacklisted before processing.
  3. Cleanup: Remove blacklisted tokens when they expire.

Example Code

const { v4: uuidv4 } = require('uuid');
const token = jwt.sign({ userId: 1, jti: uuidv4() }, 'secret', { expiresIn: '1h' });

async function blacklistToken(jti) {
  await redis.set(`blacklist:${jti}`, 'true', 'EX', 3600); // 1 hour expiry
}

async function verifyToken(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: 'No token' });
  try {
    const decoded = jwt.verify(token, 'secret');
    const isBlacklisted = await redis.get(`blacklist:${decoded.jti}`);
    if (isBlacklisted) return res.status(401).json({ error: 'Token revoked' });
    req.user = decoded;
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
}
  

Pros

  • Revokes specific tokens instantly.
  • Works with existing JWTs.

Cons

  • Requires server storage, reducing statelessness.
  • Blacklist can grow if not cleaned up.

3. Change the Secret Key

Change the secret key used to sign JWTs to invalidate all tokens. This is a last resort for major security issues.

Example

const newSecret = 'new-super-secret-key';
const token = jwt.sign({ userId: 1 }, newSecret, { expiresIn: '1h' });
  

Pros

  • Revokes all tokens instantly.
  • Simple to implement.

Cons

  • Forces all users to re-authenticate.
  • Not practical for individual revocations.

4. Token Version or Flag

Add a tokenVersion to the JWT and user’s database record. To revoke, update the user’s version, invalidating their tokens.

Example Code

const token = jwt.sign({ userId: 1, tokenVersion: user.tokenVersion }, 'secret', { expiresIn: '1h' });

async function revokeToken(userId) {
  await db.users.update({ id: userId }, { tokenVersion: user.tokenVersion + 1 });
}

async function verifyToken(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  try {
    const decoded = jwt.verify(token, 'secret');
    const user = await db.users.find({ id: decoded.userId });
    if (user.tokenVersion !== decoded.tokenVersion) {
      return res.status(401).json({ error: 'Token revoked' });
    }
    req.user = decoded;
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
}
  

Pros

  • Revokes tokens per user.
  • No need to store every token.

Cons

  • Requires database lookups.
  • Adds complexity to user management.

5. Client-Side Deletion

Ask the client to delete the JWT (e.g., from localStorage) on logout. This relies on short expiration times for security.

Example

localStorage.removeItem('token');
  

Pros

  • Simple and stateless.

Cons

  • Not secure for compromised tokens.
  • Depends on client cooperation.

Best Practices

  • Short Expirations: Use short JWT lifespans (e.g., 5–60 minutes).
  • Combine Methods: Pair refresh tokens with blacklists or version checks for flexibility.
  • Secure Storage: Use fast databases like Redis for refresh tokens or blacklists.
  • HTTPS: Always use secure connections to transmit JWTs.
  • Avoid Sensitive Data: Don’t put secrets in the payload—it’s readable!

Which Method to Choose?

  • Most Apps: Use short-lived tokens with refresh tokens.
  • High-Security Apps: Add blacklists or token versions.
  • Emergencies: Change the secret key (but it affects everyone).
  • Simple Apps: Rely on client-side deletion with short expirations.

Conclusion

Revoking JWTs requires some planning since they’re stateless, but with short-lived tokens, refresh tokens, or blacklists, you can make it work securely. Choose the method that fits your app’s needs, and always prioritize security with short expirations and secure storage. Got a specific project? Let me know, and I can help you set up the perfect revocation strategy!

Share this article

Test Your Knowledge

Ready to put what you've learned to the test? Take our interactive quiz and see how well you understand the concepts covered in this article.

Loading comments...

Leave a Comment

Share your thoughts and join the discussion!