{"record":{"id":"2b1f1c62f54689fe","repo":"nextauthjs/next-auth","slug":"failed-to-refresh-token","errorCode":null,"errorMessage":"Failed to refresh token","messagePattern":"Failed to refresh token","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/providers/fusionauth.ts","lineNumber":201,"sourceCode":"        try {\n          const refreshResponse = await fetch(\n            `${process.env.AUTH_FUSIONAUTH_ISSUER}/oauth2/token`,\n            {\n              method: 'POST',\n              headers: {\n                'Content-Type': 'application/x-www-form-urlencoded',\n              },\n              body: new URLSearchParams({\n                client_id: process.env.AUTH_FUSIONAUTH_CLIENT_ID!,\n                client_secret: process.env.AUTH_FUSIONAUTH_CLIENT_SECRET!,\n                grant_type: 'refresh_token',\n                refresh_token: token.refresh_token as string,\n              }),\n            }\n          );\n\n          if (!refreshResponse.ok) {\n            throw new Error('Failed to refresh token');\n          }\n\n          const tokensOrError = await refreshResponse.json();\n\n          if (!refreshResponse.ok) throw tokensOrError;\n\n          const newTokens = tokensOrError as {\n            access_token: string;\n            expires_in: number;\n            refresh_token?: string;\n          };\n\n          return {\n            ...token,\n            access_token: newTokens.access_token,\n            expires_at: Math.floor(Date.now() / 1000 + newTokens.expires_in),\n            // Some providers only issue refresh tokens once, so preserve if we did not get a new one\n            refresh_token: newTokens.refresh_token","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/nextauthjs/next-auth/blob/a1a16a5a7780488c7449feece410033f445d0b31/packages/core/src/providers/fusionauth.ts#L183-L219","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Log the refresh response body/status to identify the exact rejection reason","Verify AUTH_FUSIONAUTH_ISSUER, client id/secret env vars match your FusionAuth application","If the refresh token is expired/revoked, clear the session and force re-authentication","Confirm the FusionAuth application has refresh tokens enabled and reasonable TTLs"],"exampleFix":"// before\nissuer: process.env.AUTH_FUSIONAUTH_ISSUER // undefined in prod -> token endpoint 404\n// after\n// .env.production\n// AUTH_FUSIONAUTH_ISSUER=https://auth.example.com\nissuer: process.env.AUTH_FUSIONAUTH_ISSUER!, // set and verify with a health check\n// and handle refresh failures by ending the session so the user re-authenticates","handlingStrategy":"retry","validationCode":"// validate env before provider construction\nif (!process.env.AUTH_FUSIONAUTH_ISSUER || !process.env.AUTH_FUSIONAUTH_ID || !process.env.AUTH_FUSIONAUTH_SECRET) {\n  throw new Error('FusionAuth env vars (ISSUER/ID/SECRET) are not fully configured')\n}","typeGuard":"function isTokenRefreshable(t: { refresh_token?: unknown }): t is { refresh_token: string } {\n  return typeof t.refresh_token === 'string' && t.refresh_token.length > 0\n}","tryCatchPattern":"try {\n  const res = await fetch(`${issuer}/oauth2/token`, { method: 'POST', ... })\n  if (!res.ok) {\n    const body = await res.text()\n    console.error(`FusionAuth refresh failed (${res.status}):`, body)\n    if (res.status >= 500) /* retry with backoff */\n    else /* expired/revoked: end session, force re-auth */\n  }\n} catch (e) { /* network error: retry with backoff */ }","preventionTips":["Set refresh token TTLs in FusionAuth generously enough for your session lifetime","Keep AUTH_FUSIONAUTH_ISSUER/client credentials in sync across environments","On refresh failure, clear the session so the user re-authenticates instead of looping","Retry 5xx/429 with exponential backoff; never retry 4xx auth rejections"],"tags":["oauth","refresh-token","fusionauth","http-error"],"backgroundTag":"token-refresh-failed","analyzedSha":"a1a16a5a7780488c7449feece410033f445d0b31","analyzedAt":"2026-08-28T21:52:38.200Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}