Refresh Token Rotation
Last updated
This page explains how VerifyJWT() refreshes an expired Access Token within the same request, and when the Refresh ID rotates.
Triggers
A refresh happens only in these two cases; while the Access Token is valid, Redis refresh data is not touched:
| Scenario | Where the Refresh ID comes from |
|---|---|
Access Token expired (jwt expired) |
The refresh_id decoded from the expired token |
| No Access Token, but a Refresh ID | X-Refresh-ID header or the refresh cookie |
Refresh Flow
sequenceDiagram
participant C as Client
participant A as VerifyJWT
participant R as Redis
participant U as checkUserExists
C->>A: Expired Access Token or Refresh ID only
A->>R: GET refresh:id, compare fingerprint
A->>R: TTL refresh:id, version + 1
opt version > 5 or remaining TTL < refreshTokenExpires / 2
A->>R: SETEX old key 5 seconds
A->>A: New Refresh ID, version reset to 0
end
A->>R: SETEX refresh:id (refreshTokenExpires)
A->>U: Does the user exist
A-->>C: New Access Token + Refresh ID (Set-Cookie and X-New-*)
Rotation Rules
| Condition | Behavior |
|---|---|
| Every refresh | version + 1, expires_at extended, Redis TTL reset to the full refreshTokenExpires |
version > 5 |
Issue a new Refresh ID |
Remaining TTL below refreshTokenExpires / 2 |
Issue a new Refresh ID |
| On rotation | The old key stays for 5 seconds so concurrent requests can still refresh with the old ID |
CreateJWT() stores refresh data with version 1: the first 4 refreshes keep the same ID, and the 5th pushes version to 6 and rotates. After rotation version resets to 0, so the next rotation happens on the 6th refresh.
Each refresh resets the TTL to the full lifetime, so Refresh ID expiry is sliding: an actively used session never expires, and only goes stale after refreshTokenExpires of inactivity.
What a Refresh Writes Back
| Location | Content |
|---|---|
X-New-Access-Token header |
The new Access Token |
X-New-Refresh-ID header |
The current Refresh ID (new value on rotation) |
| Access cookie | New Access Token, lifetime accessTokenExpires |
| Refresh cookie | Refresh ID, lifetime refreshTokenExpires |
The new Access Token payload is the user snapshot stored at login, plus fp, version, and refresh_id.
Related: Verification Results, Redis Keys