Engineering notes
Why users get logged out at random, and how to fix JWT session bugs
By Muhammad Shahid ยท ยท 3 min read
Quick answer
Random logouts with JWT sessions almost always come from a few causes: two requests refreshing the same token at once (often from two tabs), expiry checks hit by clock differences, cookie settings that drop the token, or servers that do not share the same secret. Reproduce the logout, find which one it is, then fix that cause. I fixed JWT session bugs like these on TheCryptoBasic, a crypto news platform with 2M+ users.
| Symptom | Users logged out at random, often after some idle time or with several tabs open |
|---|---|
| Most common cause | Refresh token races between parallel requests or tabs |
| Other causes | Clock skew, cookie settings, secrets that differ between servers |
| Where I fixed it | TheCryptoBasic, 2M+ users |
| Stack | Node.js, Express, JWT |
Why are random logouts so hard to track down?
Because they depend on timing. The same user doing the same thing is logged in one time and logged out the next. Before changing code, log every authentication failure with its reason (expired, invalid signature, refresh token not found, refresh token already used) and the time on the server. The reason column usually points straight at the cause.
Cause 1: two requests refresh the same token at once
The access token expires. The page fires three API calls at the same moment; all three get a 401 and all three try to refresh. With refresh token rotation, the first refresh succeeds and invalidates the old refresh token, so the other two fail and the app logs the user out. Two open tabs do the same thing.
Fix it on the client by sharing one refresh at a time:
let refreshing = null
function refreshOnce() {
if (!refreshing) {
refreshing = api.post("/auth/refresh").finally(() => { refreshing = null })
}
return refreshing // every caller waits for the same refresh
}
On the server, accept a refresh token that was rotated a few seconds ago and return the same new pair, so a duplicate refresh in that short window is not treated as theft.
Cause 2: expiry and clock differences
If the server that issues tokens and the server that checks them disagree on the time, a fresh token can look expired. Very short access tokens make this worse. Keep server clocks synced, allow a small clock tolerance when verifying, and refresh a little before expiry rather than after the first 401.
jwt.verify(token, secret, { clockTolerance: 30 }) // seconds
Cause 3: cookie settings drop the token
If the refresh token lives in a cookie, check its settings: the domain and path must cover the API, SameSite must allow the request your app makes, Secure requires HTTPS, and the cookie's own expiry must not be shorter than the refresh token's. A cookie that silently is not sent looks exactly like a random logout.
Cause 4: servers that do not share the same secret
Behind a load balancer, every instance must sign and verify with the same secret or key set. If one instance has an old or different secret, users are logged out whenever a request lands there, which looks random. The same happens during a secret rotation done without accepting both keys for a while.
How do you confirm the fix?
- Open the app in two tabs, wait for the access token to expire, and use both.
- Fire several API calls at once right after expiry.
- Watch the authentication failure log by reason; the logout reason you saw before should disappear.
Frequently asked questions
Why does my app log users out randomly?
Usually a refresh race (several requests or tabs refreshing at once), clock differences, cookie settings, or servers with different secrets. Log every auth failure with its reason to find which.
What is a refresh token race condition?
Several requests try to refresh an expired token at the same time; with rotation, the first one invalidates the token the others are using, so they fail and the user is logged out.
How do I stop parallel refresh requests?
Share one refresh promise on the client so every request waits for the same refresh, and allow a short grace period for a just-rotated token on the server.
What is JWT clock skew?
A difference in time between servers, which can make a valid token look expired. Sync clocks and allow a small clockTolerance when verifying.
Should JWTs be stored in cookies or local storage?
Many apps keep the refresh token in an httpOnly, Secure cookie and the short-lived access token in memory, which limits what a script can steal.
Can you fix authentication bugs in my app?
Yes. I fixed JWT session bugs that logged users out at random on a 2M-user platform. See the Node.js backend service page.