DEV Community

Cover image for Essential JWT Security (Part 2): Refresh Tokens and Revocation Made Simple
Rahul Kumar
Rahul Kumar

Posted on

Essential JWT Security (Part 2): Refresh Tokens and Revocation Made Simple

Mastering JWT Security: Refresh Tokens, Revocation, and Real Logout

So, before we dive in: here’s Part 1Essential JWT Security Best Practices for Developers — check it out if you haven’t already. → https://dev.to/rahuls24/essential-jwt_security_best_practices_for_developers-7k5 ([DEV Community][1])


Alright, so last time we talked about JWTs and how people (me included, years ago) manage to mess them up. LocalStorage? Bad idea. Weak secrets? Been there, done that.

But here’s the thing that really kept me up at night: once you hand out a JWT, how the hell do you take it back?

They’re “stateless,” which sounds great on paper. Until a user clicks “log out,” closes their laptop, and then you realize… that token is still perfectly valid until it expires. If someone swiped it, they’re chilling in your app like nothing happened.

That’s the “can’t take it back” problem.


Daily Pass vs Season Pass

The way the pros handle it is by splitting the job in two. Think of it like theme park tickets:

  • The access token is your day pass. It works everywhere in the park, but only until the day’s over. In app terms, maybe 15 minutes. If it gets stolen, eh, it’s worthless soon anyway.
  • The refresh token is the season pass. It doesn’t let you straight onto the rides — it just gets you another daily pass. This one lasts longer, maybe a week or a month, and you need to guard it.

When I first heard this, it clicked. Why risk one all-powerful token when you can just rotate the short-lived one?


Where Do You Put These Things?

Here’s where I see a lot of devs (and past me again) fumble: storing tokens.

The rule of thumb:

  • Refresh token goes into an HttpOnly cookie. This is the “special secure pocket.” Your JS can’t touch it, so XSS attacks have a harder time grabbing it.
  • Access token just hangs out in memory. No fancy storage. When the tab closes, poof, it’s gone.

Something like this in Express:

const { accessToken, refreshToken } = issueTokens(user);

res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'Strict',
});

res.json({ accessToken });
Enter fullscreen mode Exit fullscreen mode

One in the cookie jar, one in your app’s hand.


Keeping the User Happy

Now, nobody wants to get booted out every 15 minutes. I built an app once that did exactly that (because I only used short-lived access tokens)… and yeah, the users hated me.

The fix: refresh quietly.

  • If the app starts up, or
  • if you hit an API and get a 401 Unauthorized

…just hit your /refresh_token endpoint in the background. The browser sends the refresh token cookie along, server checks it in the DB, and if it’s legit, hands you back a new access token. Your app swaps it in, retries the request, and the user has no idea anything happened. Smooth.


Finally: The Power to Revoke

Here’s the best part. Because refresh tokens live in your database, you can actually take them back.

  • User logs out? Kill their refresh token.
  • Password change? Wipe all tokens for that user.
  • Something sketchy going on? Nuke their tokens from orbit.

And just like that, the stateless JWT problem isn’t a problem anymore. You keep the speed and scale of access tokens, but you get the control of a normal session system.


Final Thoughts

If you’re building something serious, this setup is basically non-negotiable. I learned the hard way. Hopefully you don’t have to.

The tricky part about JWTs is that they look deceptively simple at first — “just sign a token and you’re done.” But the moment you need real-world features like logout, password resets, or revoking sessions on suspicious activity, you realize plain JWTs don’t cut it.

That’s where the access + refresh token model shines. It gives you speed and statelessness where you want it, but control when you need it.

If you’ve made it this far, I’d say you’re already ahead of the curve — most apps in the wild are still doing the bare-minimum JWT setup. Get this part right, and you’ll save yourself a lot of late-night “why is this user still logged in?!”

And hey, if you’ve got your own battle stories or tricks for handling JWTs, I’d love to hear them in the comments. That’s how we all level up together.

Top comments (0)