Skip to content

Replay resistant JWT sessions

Design notes for limiting replay when bearer tokens leak, including jti, session state, device signals, rotation, and proof of possession.

Published on 3 min read

Bearer JWTs are simple because possession is enough. That is also the problem. If an attacker gets the token from logs, malware, a proxy, a browser extension, or a pasted support ticket, the API usually cannot tell the difference between attacker and user. Replay resistance is about narrowing that gap without pretending a JWT magically became a hardware bound credential.

Start with the leak paths

Before adding mechanisms, map where tokens travel. Authorization headers pass through load balancers, application logs, APM agents, error reporters, reverse proxies, and sometimes customer controlled integrations. Browser tokens may appear in session replay tools or copied curl commands. Mobile tokens may land in crash reports. Every replay design fails if the organization keeps duplicating bearer tokens into places attackers can read.

Short lifetimes help, but only after leakage is reduced. A five minute token in verbose logs is still a five minute remote shell for the API if it grants broad access. Scope and audience matter as much as duration.

jti and server side session state

Adding jti gives each token an identifier. It becomes useful when the verifier can check that identifier against session state, a revocation list, or a rotation chain. Without a lookup, jti is just a label. With a lookup, you regain some session semantics: revoke one device, detect reuse after refresh rotation, and invalidate tokens after credential reset.

Detecting replay patterns

Do not block solely because an IP changes. Mobile networks and corporate VPNs make that noisy. Instead combine signals: impossible travel, user agent family shifts, device id changes, reuse of an old refresh token, and access from a region the account has never used. Log enough to investigate, but avoid storing full tokens. Hash jti or session ids if logs leave the trust boundary.

Stronger options

Proof of possession approaches bind the token to a key the client must use per request. DPoP and mTLS are examples, but both add operational cost. They make sense for high value APIs, machine clients, partner integrations, and administrative surfaces. For normal browser sessions, well managed refresh rotation, HttpOnly cookies, narrow scopes, and fast revocation may be the better tradeoff.

Practical design

Treat replay resistance as layers. Reduce token exposure, keep access tokens short, rotate refresh tokens, store session state server side, and alert on reuse signals. A JWT can remain the request credential while the session model lives outside it. That split is often the difference between "stolen once, valid everywhere" and "stolen once, contained quickly."

Related articles

How to inspect JWT problems safely in logs, support tickets, CLI tools, screenshots, and incident channels without spreading bearer tokens.
The messy edge cases behind JWKS caching, signing key rotation, stale kid values, and outage resistant verifier design.
Operational guidance for JWT signing keys: generation, storage, KMS use, rotation, access control, audit trails, and emergency response.