Playground API
DocsStatsBlogStudio
Documentation Tree
Technical BlogFeature Deep Dives
  • Introduction
  • Quickstart
    5 min
  • How It Works
  • Recipes & Cookbooks
  • Platform Comparisons
  • Real-World Showcase
  • Interactive Studio
    Studio
  • GraphiQL IDE
    IDE
  • Session Quotas & Activity
  • Network Chaos Simulator
  • Atomic Sandbox Reset
  • Overview & Models
    Hub
  • Users Resource
  • Posts Resource
  • Comments Resource
  • Todos Resource
  • Custom Collections
    Custom
  • Multipart File Uploads
    Upload
  • Dynamic SVG Avatars
    SVG
  • Image Thumbnails
    CDN
  • Relational Filtering
  • Full-Text Search
  • Dynamic Sorting
  • Offset Pagination
  • Cursor Pagination
    Scroll
  • CSV & Excel Export & Import
    IO
  • Custom Collections
    CRUD
  • Overview & Architecture
    Hub
  • JWT Auth Flow
  • Refresh Token Rotation
    Mutex
  • RBAC Permission Matrix
    Roles
  • Expiry Simulation
  • Clock Skew Drift
  • Password Recovery Loop
  • Dual-Mode Sandboxing
  • GraphiQL IDE
    IDE
  • Relational Queries
  • Stateful Mutations
  • Realtime Subscriptions
  • Overview & Flowcharts
    Hub
  • Hosted Checkout
    Stripe
  • Payment Intents API
  • 3DS Challenge Modal
    Modal
  • Customers Vault
  • Charges & Refunds
  • Test Cards Catalog
  • Overview & Channels
    Hub
  • Virtual Email Mailbox
    Mailtrap
  • Virtual SMS Terminal
    Phone
  • In-App Notifications
  • Message Dispatcher
  • Realtime Studio
    Studio
  • Native WebSocket (/ws)
  • Socket.io Gateway
  • Presence & Echo Bot
  • Server-Sent Events (SSE)
    SSE
  • Analytics Telemetry
  • Webhook Subscriptions
  • HMAC SHA-256 Signatures
  • Delivery Logs
  • Manual Retry Simulator
  • Network Latency Delay
  • HTTP Status Codes
  • Rate-Limit Simulator
    429
  • Flaky Network & Jitter
    Chaos
  • Session Quotas & Activity
  • JSON Snapshots
    JSON
  • Headless CI/CD Testing
    CI
  • Mobile QR Code Sync
  • System Metrics & Health
  • Atomic Sandbox Reset
  • Official TypeScript SDK
  • Multi-Language Generators
  • DevTools Extension
  • OpenAPI 3.1 Spec
    JSON
  • Postman Collection v2.1
  • Bruno Collection
  • Insomnia Workspace
  • TypeScript .d.ts
    .d.ts
  • AI Prompt Rules
    Rules
  • Context Index (llms.txt)
  • Full Schema (llms-full.txt)
  • Manifest (product.json)
  • All Feature Articles
    Blog
  • React CRUD Without Backend
    Deep Dive
  • Why Static APIs Fail
  • Mocking Stateful Auth
  • WebSockets & SSE Guide
Technical Blog
Articles

In-depth articles explaining stateful mock APIs, WebSockets, payments, and frontend resilience.

Read Articles
PreviousJWT Auth Flow
NextRBAC Permission Matrix
Auth & Security

Refresh Token Rotation & Mutex Locking

Mutex Guard

Implement bulletproof session lifecycle management using single-use Refresh Token Rotation (RTR). Protect your single-page app against token theft with automatic token family invalidation and prevent browser race conditions using client-side mutex request queuing.

Interactive Rotation & Threat Simulator

Select an operational mode to test token exchange, short-lived renewals, or simulate a token reuse breach:

Execute: Standard Token Rotation

POST
response.json
1
{
2
// Click "Send" above to execute this request against the live server.
3
}

Threat Model: Token Reuse Detection (RFC 6749)

How token families detect stolen refresh tokens and trigger automatic session invalidation:

01

Normal Token Rotation

Legitimate user exchanges Token A for Token B. The gateway consumes Token A and issues Token B with the same family ID.

02

Attacker Replay Attempt

An attacker who previously intercepted Token A attempts to exchange it. The server detects that Token A has already been consumed.

03

Total Family Revocation

The authorization gateway treats reuse as an active breach, immediately revoking Token B as well, forcing all parties to re-authenticate.

Client-Side Concurrency Mutex Pattern

When multiple concurrent API calls fail with 401 at the same instant (e.g., initial page load with 5 parallel widgets), only one refresh request must fire. All other calls wait in a pending queue:

authInterceptor.ts
1
// Production Axios Response Interceptor with Mutex Lock & Request Queue
2
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
3
4
export const api = axios.create({
5
baseURL: 'https://playground.nileslabs.com/api/v1',
6
});
7
8
let isRefreshing = false;
9
let failedQueue: Array<{
10
resolve: (token: string) => void;
11
reject: (err: any) => void;
12
}> = [];
13
14
const processQueue = (error: any, token: string | null = null) => {
15
failedQueue.forEach((prom) => {
16
if (error) prom.reject(error);
17
else if (token) prom.resolve(token);
18
});
19
failedQueue = [];
20
};
21
22
// Response Interceptor: Intercept 401s and queue parallel requests
23
api.interceptors.response.use(
24
(response) => response,
25
async (error: AxiosError) => {
26
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
27
28
// Detect 401 and avoid infinite retry loops
29
if (error.response?.status === 401 && !originalRequest._retry) {
30
if (isRefreshing) {
31
// Mutex Active: Queue this concurrent request until the token arrives
32
return new Promise<string>((resolve, reject) => {
33
failedQueue.push({ resolve, reject });
34
}).then((newToken) => {
35
originalRequest.headers.Authorization = Bearer ${newToken};
36
return api(originalRequest);
37
});
38
}
39
40
originalRequest._retry = true;
41
isRefreshing = true;
42
43
try {
44
const storedRefreshToken = localStorage.getItem('refresh_token');
45
const { data } = await axios.post('https://playground.nileslabs.com/api/v1/auth/refresh', {
46
refreshToken: storedRefreshToken,
47
});
48
49
const newAccessToken = data.access_token;
50
const newRefreshToken = data.refresh_token;
51
52
localStorage.setItem('access_token', newAccessToken);
53
localStorage.setItem('refresh_token', newRefreshToken);
54
55
// Resume all queued concurrent requests
56
processQueue(null, newAccessToken);
57
58
originalRequest.headers.Authorization = Bearer ${newAccessToken};
59
return api(originalRequest);
60
} catch (refreshErr) {
61
processQueue(refreshErr, null);
62
// Force redirect to login on token reuse or expired family
63
localStorage.clear();
64
window.location.href = '/login?session_expired=true';
65
return Promise.reject(refreshErr);
66
} finally {
67
isRefreshing = false;
68
}
69
}
70
71
return Promise.reject(error);
72
}
73
);