{"record":{"id":"167860245cd8d69d","repo":"decolua/9router","slug":"token-refresh-failed-error","errorCode":null,"errorMessage":"Token refresh failed: ${error}","messagePattern":"Token refresh failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/kiro.js","lineNumber":201,"sourceCode":"      assertValidAwsRegion(safeRegion);\n      const endpoint = `https://oidc.${safeRegion}.amazonaws.com/token`;\n\n      const response = await fetch(endpoint, {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n        body: JSON.stringify({\n          clientId,\n          clientSecret,\n          refreshToken,\n          grantType: \"refresh_token\",\n        }),\n      });\n\n      if (!response.ok) {\n        const error = await response.text();\n        throw new Error(`Token refresh failed: ${error}`);\n      }\n\n      const data = await response.json();\n      return {\n        accessToken: data.accessToken,\n        refreshToken: data.refreshToken || refreshToken,\n        profileArn: data.profileArn,\n        expiresIn: data.expiresIn,\n      };\n    }\n\n    // Social auth refresh (Google/GitHub)\n    const response = await fetch(`${KIRO_AUTH_SERVICE}/refreshToken`, {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },\n      body: JSON.stringify({","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/kiro.js#L183-L219","documentation":"Thrown by KiroService.refreshToken on the AWS SSO OIDC path (used when clientId and clientSecret are present, i.e. Builder ID / IDC device-flow accounts). The refresh call to oidc.<region>.amazonaws.com/token returned non-2xx and the AWS error body is included in the message. The stored refresh token could not be traded for a new access token.","triggerScenarios":"POST to oidc.<region>.amazonaws.com/token with grantType refresh_token returns !response.ok — typically invalid_grant because the refresh token was revoked/expired, an expired clientSecret, or a wrong region.","commonSituations":"User revoked the device authorization in AWS Builder ID settings; token idle past AWS's refresh-token lifetime; clientSecretExpiresAt passed; account moved between regions; AWS throttling.","solutions":["Treat this as an expired/revoked session: re-run the full device-code login (registerClient → startDeviceAuthorization → pollDeviceToken) and store the new tokens.","If the body shows expired client secret, call registerClient again for fresh clientId/clientSecret before refreshing.","Check the embedded AWS error: invalid_grant means re-auth is required; slow_down/throttling means retry with backoff.","Verify the persisted region matches the one the token was issued in."],"exampleFix":"// before: refreshing forever with a dead token\ntry { await svc.refreshToken(rt, { clientId, clientSecret, region }); } catch { /* retry loop */ }\n// after: re-authenticate on invalid_grant\ntry {\n  await svc.refreshToken(rt, { clientId, clientSecret, region });\n} catch (e) {\n  if (/invalid_grant|InvalidRefreshToken/i.test(e.message)) return deviceCodeLogin();\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"function canAttemptOidcRefresh(psd) {\n  return typeof psd?.clientId === 'string' && psd.clientId.length > 0 &&\n         typeof psd?.clientSecret === 'string' && psd.clientSecret.length > 0 &&\n         typeof psd?.refreshToken === 'string' && psd.refreshToken.startsWith('aorAAAAAG');\n}\nif (!canAttemptOidcRefresh(providerSpecificData)) throw new Error('Incomplete OIDC refresh credentials — re-authenticate');","typeGuard":"function isRefreshResult(r) { return typeof r?.accessToken === 'string' && r.accessToken.length > 0; }","tryCatchPattern":"try {\n  return await svc.refreshToken(rt, { clientId, clientSecret, region });\n} catch (e) {\n  if (/invalid_grant|revoked|expired/i.test(e.message)) {\n    markAccountNeedsReauth(accountId);\n    return null; // caller shows re-login UI\n  }\n  if (/throttl|SlowDown/i.test(e.message)) return retryWithBackoff();\n  throw e;\n}","preventionTips":["Refresh proactively before expiresIn lapses instead of waiting for a 401.","Distinguish invalid_grant (re-auth required) from throttling (retry) using the embedded error body.","Store clientId/clientSecret/region with each account so the OIDC branch is always taken for Builder ID/IDC tokens.","Watch clientSecretExpiresAt and re-run registerClient when it passes."],"tags":["oauth","aws","oidc","token-refresh","expired-token"],"backgroundTag":"oauth-token-refresh-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}