MP-301g · Module 2
Token Refresh Under Load
3 min read
OAuth access tokens expire, and MCP clients must refresh them transparently. The naive approach — refresh when a request returns 401 — works but causes a latency spike on the first expired request. The better approach is proactive refresh: track the token's expiry time and refresh it before it expires, typically at 75% of the TTL. A token with a 1-hour TTL gets refreshed at the 45-minute mark. This ensures a valid token is always available, and no request takes the refresh latency hit.
Under high concurrency, token refresh creates a thundering herd problem. If 50 in-flight requests all discover the token is expired simultaneously, all 50 attempt to refresh — hammering the authorization server with 50 identical token exchange requests. The solution is a refresh mutex: the first request that detects expiry acquires the lock, performs the refresh, and updates the shared token. All other requests wait on the lock and use the refreshed token. This reduces 50 refresh requests to exactly one.
Refresh token rotation adds another layer of complexity. Some authorization servers issue a new refresh token with each access token refresh, invalidating the old one. If two concurrent refresh attempts use the same refresh token, one succeeds and the other invalidates the new refresh token — locking the client out entirely. The refresh mutex prevents this, but you must also handle the case where the refresh token itself expires or is revoked. At that point, the client must re-authenticate the user from scratch. Surface this as a clear error, not a silent retry loop.
- Implement proactive refresh Store the token's expires_at timestamp. Start a refresh attempt at 75% of the TTL. If the refresh fails, retry with backoff but do not block requests until the token actually expires.
- Add the refresh mutex Use a promise-based lock (or mutex) around the refresh call. The first caller refreshes; concurrent callers await the same promise and share the result.
- Handle refresh token rotation After each refresh, update both the access token and the refresh token atomically. If the refresh token is rejected (400 or 401), clear all tokens and trigger re-authentication.