Home / Blog / Encoding & Base64

Hash crypto & JWT

Refresh a JWT Access Token with One Coordinated Renewal

Implement the renewal control flow around an authorization server, avoid concurrent refresh races, and retry an API operation only under a bounded policy.

Renew through the issuer instead of editing the token

To refresh a JWT access token, use the authorization server's supported renewal flow. The client does not extend a token by decoding its payload, changing exp and re-encoding the result. That changes the signed content and does not produce a new token trusted by the issuer.

Determine whether the provider issued a refresh token and whether the application is allowed to use it. Some sessions require another interactive sign-in instead. The client's code should represent that distinction explicitly rather than assuming every token-shaped value can be renewed indefinitely.

Coordinate concurrent renewal attempts

function singleFlightRefresh(refresh) {
  let pending = null;
  return function renewOnce() {
    if (!pending) {
      pending = Promise.resolve().then(refresh).finally(() => {
        pending = null;
      });
    }
    return pending;
  };
}

This small helper makes simultaneous callers share one pending renewal promise within one JavaScript instance. The supplied refresh function performs the provider-specific request and the credential-state update. The helper does not know a token endpoint, choose a storage mechanism or validate an authorization response.

After success or failure, the pending reference is cleared so a later deliberate attempt can run. If the underlying refresh function throws before returning a promise, Promise.resolve().then still turns that failure into the shared rejected promise. Callers must handle the rejection rather than treating completion as success.

Recognize the scope of the coordination

This in-memory helper coordinates callers in one runtime. It does not coordinate separate browser tabs, worker processes, server instances or devices. A deployment with shared rotating credentials needs an appropriate cross-instance strategy or separate sessions so that two independent processes do not both consume the same token.

Coordinate the credential update with renewal completion. The refresh function should finish only after the new state is available to callers that will retry their API requests. Returning early and saving a replacement token in an unrelated background task can reintroduce the race the helper was intended to prevent.

Use the provider's request and client-authentication rules

The OAuth refresh grant describes the token exchange, including its form-encoded grant parameters. Providers can have additional documented requirements. Keep this request separate from the protected-resource request and avoid sending a refresh credential to an arbitrary API endpoint.

Apply a bounded network timeout and preserve whether the operation failed with a readable token error or an uncertain network outcome. A timeout after server-side rotation can leave the client unsure which credential is current. Follow the provider's documented recovery behavior rather than blindly replaying a one-time credential in a tight loop.

Retry the original operation under a deliberate policy

After successful renewal, rebuild the authorized API request with the new access token. Allow only the retry behavior the application intends. If the request is rejected again, stop the renewal loop and expose an appropriate error or sign-in state.

Do not renew automatically for every 403, 404 or 500 response. A missing permission, absent resource or server defect is not necessarily an expired credential. Use the API's documented authentication error signals and verify that a retry is appropriate for the original operation, especially if it could have side effects.

When a request body is a one-use stream, it may not be reusable after the first attempt. Build a fresh request from retained safe input when the design allows a retry, or decline automatic replay. Merely changing the Authorization header on a consumed Request object is not a complete retry implementation.

Prove success, failure and recovery behavior

Test two simultaneous callers and assert that the refresh function runs once and both receive the same outcome. Then test a rejected refresh, confirm that all waiting callers see the rejection, and verify that a later authorized attempt is no longer stuck behind the old promise.

Use the JWT Decoder only to inspect synthetic claims while debugging this flow. It does not contact an authorization server or create a valid renewal. The OAuth security guidance is the reference for the wider token-replay and client-security design.