The Normal JWT Request Flow

sequenceDiagram
    participant User as User
    participant Frontend as Frontend (browser/app)
    participant Auth as Auth server
    participant Backend as Backend API server

    Note over User,Backend: 1. Login phase
    User->>Frontend: enter username/password
    Frontend->>Auth: POST /login (credentials)
    Auth->>Auth: verify credentials
    Auth-->>Frontend: return access_token + refresh_token
    Frontend->>Frontend: store tokens (memory/localStorage)
    Frontend-->>User: login successful

    Note over User,Backend: 2. Normal request phase
    User->>Frontend: request a protected resource
    Frontend->>Backend: GET /api/resource
Authorization: Bearer access_token Backend->>Backend: verify access_token signature and expiry Backend-->>Frontend: return the requested resource Frontend-->>User: display data Note over User,Backend: 3. Access token expires User->>Frontend: keep requesting Frontend->>Backend: GET /api/resource
Authorization: Bearer access_token (expired) Backend-->>Frontend: 401 Unauthorized (token expired) Frontend->>Frontend: detects 401, triggers refresh logic Note over Frontend,Auth: 4. Token refresh phase Frontend->>Auth: POST /refresh
refresh_token Auth->>Auth: verify refresh_token Auth-->>Frontend: return a new access_token (optionally a new refresh_token) Frontend->>Frontend: update the stored access_token Frontend->>Backend: retry the original request (new access_token) Backend-->>Frontend: return the requested resource Frontend-->>User: display data

Why You Can’t Rely on JWT Alone

  1. Once a refresh token is issued and the backend doesn’t store it, it becomes an uncontrollable long-term pass
  2. You can’t forcibly log a user out
  3. You can’t log out a single device
  4. You can’t tell whether a token has been stolen (an attacker who gets the refresh token can keep refreshing the access token forever)
  5. You can’t implement session management

The Flow After Using a refresh_tokens Table

sequenceDiagram
    participant User as User
    participant Frontend as Frontend
    participant Auth as Auth server
    participant DB as Database (refresh_tokens table)
    participant Backend as Backend API server

    Note over User,DB: 1. Login phase (issue token + persist)
    User->>Frontend: enter username/password
    Frontend->>Auth: POST /login
    Auth->>Auth: verify credentials
    Auth->>DB: generate a unique refresh_token_id
INSERT INTO refresh_tokens
(user_id, token_hash, expires_at,
device_info, ip_address, revoked) DB-->>Auth: insert successful Auth->>Auth: generate access_token + refresh_token
(refresh_token contains the id reference) Auth-->>Frontend: return access_token + refresh_token Frontend->>Frontend: store tokens Frontend-->>User: login successful Note over User,Backend: 2. Normal request (same as before) User->>Frontend: request a resource Frontend->>Backend: GET /api/resource
Authorization: Bearer access_token Backend->>Backend: verify access_token Backend-->>Frontend: return the resource Note over Frontend,DB: 3. Access token expires → refresh Frontend->>Backend: GET /api/resource (access_token expired) Backend-->>Frontend: 401 Unauthorized Frontend->>Auth: POST /refresh
refresh_token Note over Auth,DB: 4. Refresh verification (multi-step checks) Auth->>Auth: parse refresh_token, extract token_id Auth->>DB: SELECT * FROM refresh_tokens
WHERE id = token_id DB-->>Auth: return the record Auth->>Auth: verification checklist: Note over Auth: ✅ does token_hash match
✅ is it expired (expires_at > now())
✅ is it revoked (revoked = false)
✅ is the user still valid
✅ does device info match (optional) alt all checks pass Auth->>DB: UPDATE refresh_tokens
SET last_used_at = now(),
last_used_ip = current_ip
WHERE id = token_id Auth->>Auth: generate a new access_token
(optional: extend refresh_token validity) Auth-->>Frontend: return the new access_token Frontend->>Frontend: update access_token Frontend->>Backend: retry the original request (new token) Backend-->>Frontend: return the resource else checks fail Auth-->>Frontend: 401/403 (refresh failed) Frontend->>Frontend: clear all tokens Frontend->>User: redirect to the login page end Note over User,DB: 5. Explicit logout User->>Frontend: click logout Frontend->>Auth: POST /logout
refresh_token Auth->>DB: UPDATE refresh_tokens
SET revoked = true
WHERE id = token_id Auth-->>Frontend: logout successful Frontend->>Frontend: clear local tokens Frontend-->>User: logged out Note over User,DB: 6. Security scenario: password change User->>Frontend: change password Frontend->>Auth: POST /change-password Auth->>DB: UPDATE refresh_tokens
SET revoked = true
WHERE user_id = current_user_id Note over DB: revoke all of the user's refresh_tokens
(force all devices to log in again) Auth-->>Frontend: password change successful Frontend->>Frontend: clear local tokens Frontend-->>User: please log in again