nextauthjs/next-auth · error · Error

Failed to refresh token

Error message

Failed to refresh token

What it means

Thrown when the POST to FusionAuth's /oauth2/token endpoint (grant_type=refresh_token) returns a non-OK response. The stored refresh token could not be exchanged for a new access token, so the provider gives up rather than continuing with an expired session. The failure is silent about the API body here — check the refresh response separately for details.

Source

Thrown at packages/core/src/providers/fusionauth.ts:201

        try {
          const refreshResponse = await fetch(
            `${process.env.AUTH_FUSIONAUTH_ISSUER}/oauth2/token`,
            {
              method: 'POST',
              headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
              },
              body: new URLSearchParams({
                client_id: process.env.AUTH_FUSIONAUTH_CLIENT_ID!,
                client_secret: process.env.AUTH_FUSIONAUTH_CLIENT_SECRET!,
                grant_type: 'refresh_token',
                refresh_token: token.refresh_token as string,
              }),
            }
          );

          if (!refreshResponse.ok) {
            throw new Error('Failed to refresh token');
          }

          const tokensOrError = await refreshResponse.json();

          if (!refreshResponse.ok) throw tokensOrError;

          const newTokens = tokensOrError as {
            access_token: string;
            expires_in: number;
            refresh_token?: string;
          };

          return {
            ...token,
            access_token: newTokens.access_token,
            expires_at: Math.floor(Date.now() / 1000 + newTokens.expires_in),
            // Some providers only issue refresh tokens once, so preserve if we did not get a new one
            refresh_token: newTokens.refresh_token

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Log the refresh response body/status to identify the exact rejection reason
  2. Verify AUTH_FUSIONAUTH_ISSUER, client id/secret env vars match your FusionAuth application
  3. If the refresh token is expired/revoked, clear the session and force re-authentication
  4. Confirm the FusionAuth application has refresh tokens enabled and reasonable TTLs

Example fix

// before
issuer: process.env.AUTH_FUSIONAUTH_ISSUER // undefined in prod -> token endpoint 404
// after
// .env.production
// AUTH_FUSIONAUTH_ISSUER=https://auth.example.com
issuer: process.env.AUTH_FUSIONAUTH_ISSUER!, // set and verify with a health check
// and handle refresh failures by ending the session so the user re-authenticates
Defensive patterns

Strategy: retry

Validate before calling

// validate env before provider construction
if (!process.env.AUTH_FUSIONAUTH_ISSUER || !process.env.AUTH_FUSIONAUTH_ID || !process.env.AUTH_FUSIONAUTH_SECRET) {
  throw new Error('FusionAuth env vars (ISSUER/ID/SECRET) are not fully configured')
}

Type guard

function isTokenRefreshable(t: { refresh_token?: unknown }): t is { refresh_token: string } {
  return typeof t.refresh_token === 'string' && t.refresh_token.length > 0
}

Try / catch

try {
  const res = await fetch(`${issuer}/oauth2/token`, { method: 'POST', ... })
  if (!res.ok) {
    const body = await res.text()
    console.error(`FusionAuth refresh failed (${res.status}):`, body)
    if (res.status >= 500) /* retry with backoff */
    else /* expired/revoked: end session, force re-auth */
  }
} catch (e) { /* network error: retry with backoff */ }

Prevention

When it happens

Trigger: Calling refresh with token.refresh_token set but FusionAuth rejects it: refresh token expired or revoked (user logged out, password change, tenant policy), wrong AUTH_FUSIONAUTH_ISSUER/client credentials, network/API outage, or the token endpoint URL changed. Any !refreshResponse.ok triggers the throw.

Common situations: Users returning after long inactivity past the refresh token TTL; environments where AUTH_FUSIONAUTH_ISSUER points at the wrong host; FusionAuth admin revoking sessions; rotating client secrets without redeploying.

Related errors


AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28). Data as JSON: /api/errors/2b1f1c62f54689fe. Report an issue: GitHub.