Access vs Refresh Tokens

The Expiry Dilemma

A short access token (15 min) is safe: if it is stolen, it is useless soon. But users hate logging in every 15 minutes. A long access token (30 days) is convenient but dangerous, and you cannot revoke a stateless JWT. The answer is two tokens:

Access tokenRefresh token
PurposeCall the APIGet a new access token
Lifetime5–15 minutes7–30 days
FormatSigned JWT (stateless)Random string, stored (hashed) in the database
Sent toEvery API call, Authorization headerOnly POST /api/auth/refresh
Stored in the browserMemory (JS variable)HttpOnly; Secure; SameSite=Strict cookie
Can be revokedNo (wait for expiry)Yes (delete or mark revoked in DB)

The Flow

login ---------> 200 {accessToken: A1}  +  Set-Cookie: shopease_rt=R1 (HttpOnly)
API calls -----> Authorization: Bearer A1             ... works for 15 min
API call ------> 401 "Access token expired"
refresh -------> POST /api/auth/refresh  (cookie R1 sent automatically)
            <--- 200 {accessToken: A2}  +  Set-Cookie: shopease_rt=R2   (R1 now used up)
retry call ----> Authorization: Bearer A2  -> 200
logout --------> POST /api/auth/logout -> R2 revoked, cookie cleared

Why Not a JWT Refresh Token?

Note: You can make the refresh token a JWT too, but a random opaque string stored in the database is simpler and safer: the server must look it up anyway to support revocation and rotation. Store only its SHA-256 hash, like a password, so a database leak does not leak usable tokens.