{"record":{"id":"c30e8e0628ab1570","repo":"nextauthjs/next-auth","slug":"missing-refresh-token","errorCode":null,"errorMessage":"Missing refresh_token","messagePattern":"Missing refresh_token","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/core/src/providers/fusionauth.ts","lineNumber":181,"sourceCode":"  // the ability to call API's externally that rely on JWT tokens.\n  callbacks: {\n    async jwt(params) {\n      const { token, user, account } = params;\n      if (account) {\n        // First-time login, save the `access_token`, its expiry and the `refresh_token`\n        return {\n          ...token,\n          ...account,\n        };\n      } else if (\n        token.expires_at &&\n        Date.now() < (token.expires_at as number) * 1000\n      ) {\n        // Subsequent logins, but the `access_token` is still valid\n        return token;\n      } else {\n        // Subsequent logins, but the `access_token` has expired, try to refresh it\n        if (!token.refresh_token) throw new TypeError('Missing refresh_token');\n\n        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","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/nextauthjs/next-auth/blob/a1a16a5a7780488c7449feece410033f445d0b31/packages/core/src/providers/fusionauth.ts#L163-L199","documentation":"FusionAuth's token refresh logic throws a TypeError when it needs to refresh an expired access_token but the stored token object has no refresh_token property. Without a refresh token there is no way to obtain a new access token, so the provider fails fast. This typically indicates the initial token response did not include a refresh token (or it was not persisted).","triggerScenarios":"On a subsequent login the check Date.now() < token.expires_at * 1000 is false (access token expired), and !token.refresh_token is true — i.e. the persisted token from the initial OAuth callback lacked a refresh_token because the original token request omitted the offline_access scope or the token was stored stripped of the field.","commonSituations":"Hitting this after a session outlives the access token lifetime; after changing FusionAuth application settings so refresh tokens are no longer issued; or when a custom callback/adapter drops refresh_token when saving tokens (e.g. typed as optional and serialized as undefined).","solutions":["Request the offline access scope in the FusionAuth authorization request so a refresh_token is issued (authorization: { params: { scope: \"offline_access\" } })","Verify FusionAuth application settings allow refresh tokens and inspect the initial token response","Force a full re-login (clear cookies/session) so a fresh token set including refresh_token is obtained","Audit your adapter/callback code to ensure token.refresh_token is persisted, not stripped"],"exampleFix":"// before\nimport FusionAuth from \"@auth/core/providers/fusionauth\"\nproviders: [FusionAuth({ clientId, issuer })] // no offline scope\n// after\nproviders: [\n  FusionAuth({\n    clientId: process.env.AUTH_FUSIONAUTH_ID,\n    clientSecret: process.env.AUTH_FUSIONAUTH_SECRET,\n    issuer: process.env.AUTH_FUSIONAUTH_ISSUER,\n    authorization: { params: { scope: \"offline_access\" } },\n  })\n]","handlingStrategy":"type-guard","validationCode":"// before relying on refresh, check the stored token\nif (!token.refresh_token) {\n  // force full re-authentication instead of attempting refresh\n  await clearSession()\n}","typeGuard":"function hasRefreshToken(t: { refresh_token?: string | null }): t is { refresh_token: string } {\n  return typeof t.refresh_token === 'string' && t.refresh_token.length > 0\n}","tryCatchPattern":"try {\n  await refreshed = refreshAccessToken(token)\n} catch (e) {\n  if (e instanceof TypeError && e.message === 'Missing refresh_token') {\n    // cannot refresh: invalidate session and redirect user to sign in again\n    await signOut()\n  }\n}","preventionTips":["Request the offline_access scope so FusionAuth issues refresh tokens","Persist token.refresh_token through your adapter/session callbacks (don't strip optional fields)","Clear cookies and re-login after changing FusionAuth app token settings","Test the full session-expiry path in staging, not just first login"],"tags":["oauth","refresh-token","fusionauth","typeerror"],"backgroundTag":"missing-refresh-token","analyzedSha":"a1a16a5a7780488c7449feece410033f445d0b31","analyzedAt":"2026-08-28T21:52:38.200Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}