Skip to content
All writing Part 06 of 07 · Seven Blind Spots Behind a JWT Audit
Engineering · 3 min read

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.

Login Access (15 min) Refresh R1 (7d) API calls expires /refresh R1 active? issue R2 R1 → "used" R1 used? REUSE → revoke family expires → /refresh catches theft via reuse detection
AccessRefresh
TTL5-15 min1-7 days
Sent toevery API endpointonly /refresh
Storagememory (JS variable)httpOnly cookie
JS readableyes (Authorization header)no (blocks XSS)
audmy-appmy-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 with aud: '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

Tags #jwt #security #authentication
// connect

Be brave | Be wise | Be grateful

21 BreakinCode

// elsewhere
LinkedInMedium (lang: en)Life RecordYoutube
wh:~$William Hung· © 2026 Taipei · GMT+8 · Available for collaboration