API security

JWTs are not sessions: token handling mistakes in mobile backends

In one paragraph

The most common JWT mistakes in mobile backends are long or absent expiry, logout that discards the token client-side without server-side revocation, refresh tokens that never rotate, and putting authorisation decisions in claims the server then trusts without rechecking. Access tokens should be short-lived and refresh tokens rotated on every use.

Mobile teams reach for JWTs because they are stateless and every tutorial uses them. Then they implement a session system on top without noticing, and the mismatch between what a JWT is and what a session needs produces the same four findings in assessment after assessment.

1. The token that lives for a year

A mobile team gets tired of users being logged out, so the access token expiry goes from fifteen minutes to thirty days, or gets removed. Now a token pulled from a device backup, a log file or an analytics payload is valid for a month, and nothing can shorten that.

  • Access tokens: minutes, not days. Fifteen minutes is normal, an hour is defensible.
  • Use a refresh token for continuity. That is the entire point of having two tokens.
  • If refresh feels like too much work, the answer is a refresh implementation, not a longer access token.

2. Logout that does not log out

The app deletes the token from storage and shows the login screen. The token itself remains valid until it expires — which, given mistake one, may be next year. Anyone who captured it is unaffected by the user logging out.

Statelessness is the trade-off people forget they made. If you need revocation — and any product handling money or personal data does — you need server-side state for it.

  • Keep a revocation list keyed by token identifier, checked on each request. A short access-token lifetime keeps that list small.
  • Or keep a per-user token version: logout increments it, and any token carrying an older version is rejected.
  • Revoke on password change, on suspicious activity, and when a device is removed.

3. Refresh tokens treated like passwords

A refresh token that never rotates and never expires is a password with a worse recovery story. Rotation on every use is what makes theft detectable.

  1. Issue a new refresh token every time one is used, and invalidate the old one immediately.
  2. If a used refresh token is presented again, treat it as theft: revoke the whole family and force re-authentication.
  3. Bind the refresh token to a device identifier so a token lifted from one device is useless on another.
  4. Store it in Keystore or Keychain — never in plain preferences, and never in a place a backup will copy off the device.

4. Authorisation decided by claims the client can influence

The token carries a role claim, and the server trusts it. This is fine if the server signed it and rechecks the signature — and it is a privilege escalation the moment any code path builds a token from user-supplied input, or the algorithm handling accepts "none", or the same signing key is used across environments with different trust levels.

  • Pin the accepted algorithm explicitly. Never let the token’s header choose it.
  • Verify issuer and audience, not just the signature.
  • Recheck authorisation against the database for anything that matters. A role in a token is a cache, and caches go stale — a user demoted five minutes ago still holds a token saying otherwise.
  • Use separate signing keys per environment, and rotate them on a schedule you have actually rehearsed.
Sensible defaults for a mobile backend
ItemDefaultWhy
Access token lifetime15 minutesLimits the value of a captured token
Refresh token lifetime30 days, rotatingContinuity without a permanent credential
Refresh reuse detectedRevoke the familyTurns theft into a detectable event
LogoutServer-side revocationOtherwise the token outlives the session
Storage on deviceKeystore / KeychainPlain preferences are readable with device access
Role in claimsHint onlyAuthorisation is rechecked server side

How this gets tested

In an assessment, the sequence is always the same: log in, capture the token, log out, replay the token. If the request still succeeds, that is the finding, and its severity depends on what the token reaches. Then decode the claims and look for anything the server might trust; then present an old refresh token twice and see whether anyone notices.

None of that needs tooling beyond a proxy and a text editor, which is a fair summary of why these findings are so common.

Want this tested properly?

Scope and a fixed quote within two working days. First consultation is free.