Node.js JWT Logout Done Right — Securely Invalidating Access & Refresh Tokens
JWT authentication feels simple — until logout enters the chat.
With traditional sessions, logout is easy:
Delete session → user logged out.
But JWT is stateless.
Once you issue a token…
✔ it lives until it expires
✔ the server doesn’t store it
✔ deleting it on the client doesn’t invalidate it
So the BIG question is:
How do you securely log a user out when using JWT?
Let’s walk through the correct options — including code examples.
🧠 First — How JWT Login Usually Works
You normally issue:
Access token (short life, e.g., 15 mins)
Used on each request
Refresh token (long life, e.g., 7–30 days)
Used to get new access tokens
So logout must at least revoke the refresh token
and ideally also handle access tokens.
❌ Wrong Logout (But Many Apps Do This)
Some tutorials say:
This only deletes the token on the browser.
But…
If the attacker already stole the token — it still works.
So this is NOT real logout.
✅ Correct JWT Logout Approaches
There are 3 accepted strategies.
Pick the one that fits your app.
Method 1 — Delete Refresh Token From Backend (Most Common)
This assumes:
✔ access token expires quickly
✔ refresh token is stored server-side (DB or Redis)
When user logs out — remove refresh token from DB
Example login — storing refresh token:
Logout route
Client also deletes local tokens
Now refresh token is dead forever.
Access token will expire soon — so risk window is limited.
This is the most widely-used real-world solution.
Method 2 — Token Blacklist (Enterprise / High Security)
Use when:
✔ tokens must be revoked immediately
✔ security matters (banking, admin dashboards)
✔ access tokens cannot be trusted after logout
You store invalid tokens in Redis until they expire.
Every request checks the blacklist.
Add token to blacklist when logging out
Middleware checks blacklist
This gives instant logout — even for access tokens.
Method 3 — Rotate Refresh Tokens (Best Practice Security)
Each refresh issues a new token
and invalidates the old one.
So if an attacker steals it — it's useless later.
Logout = delete current refresh token.
This is what Auth0, Firebase, and Amazon Cognito do.
🔐 HTTP-Only Cookie Logout (If You Store JWT in Cookies)
If you use secure cookies — logout is simple:
Just make sure cookies are:
✔ httpOnly
✔ secure
✔ sameSite=strict
Never store JWT in normal cookies or localStorage for high-risk apps.
🚨 Security Rules You Should Follow
1. Short access token lifetime
Recommended:
2. Long refresh token lifetime (but revocable)
3. Store refresh tokens securely
Best:
✔ Database
✔ Redis
✔ Encrypted storage
Worst:
❌ localStorage
❌ insecure cookies
❌ memory variables
4. Always rotate refresh tokens
So an attacker can’t reuse old ones.
🧪 Logout Flow Example (Clean & Modern)
Client sends refresh token to /logout
Server deletes it from DB
Client deletes local tokens
Access token expires soon
User = logged out.
🧩 Common Bugs & Fixes
“User still logged in after logout”
Access token is still valid.
Solution: shorten access token lifetime.
“Refresh token reuse attack risk”
Fix: rotate refresh tokens always.
“403 after logout but before refresh expiry”
That’s correct — token is invalid 👍
✅ Simple Reference Implementation
Login
-
issue access token
-
issue refresh token
-
store refresh token in DB
Protected route
-
verify access token
-
deny if blacklisted
Refresh
-
verify refresh token exists in DB
-
rotate refresh token
-
issue new access token
Logout
-
delete refresh token from DB
-
(optional) blacklist current access token
Final Thoughts
JWT logout is not automatic —
you have to design it.
The best balance for most apps is:
✔ short-life access tokens
✔ refresh tokens stored in DB
✔ delete refresh token on logout
✔ optional blacklist for high-security apps
Simple. Secure. Predictable.