Access and Refresh Token Rotation
One token forces a tradeoff between security and UX. Two tokens with rotation and reuse detection shrink the attack window to 15 minutes.
One token forces a tradeoff: short TTL (secure, bad UX) or long TTL (good UX, wide attack window). Two tokens resolve this tradeoff.
| Access | Refresh | |
|---|---|---|
| TTL | 5-15 min | 1-7 days |
| Sent to | every API endpoint | only /refresh |
| Storage | memory (JS variable) | httpOnly cookie |
| JS readable | yes (Authorization header) | no (blocks XSS) |
aud | my-app | my-refresh |
Each refresh token carries a unique jti. The server tracks its state: active or used. After /refresh issues a new pair, the old token becomes used. A second use of a used token triggers reuse detection. The server revokes the entire token family. The 15-min access TTL limits the attack window. When the real user’s token expires, the /refresh call catches the theft.
Cookie flags (httpOnly is a cookie flag, not a protocol):
HttpOnly: hides the cookie from JavaScript.Secure: sends the cookie only over HTTPS.SameSite=Strict: blocks the cookie on cross-site requests.
From the audit: auth-service signs access tokens with
aud: 'my-app'and refresh tokens withaud: 'my-refresh'. During rotation, it re-fetches the user from DB for fresh email and displayName.
“One token is a tradeoff. Two tokens are the solution.”
References:
Related: back to the Seven Blind Spots Behind a JWT Audit overview · Six Standard Claims · The iss Claim · Algorithm Confusion and the none Attack