decolua/9router · error
Token exchange failed: ${error}
Error message
Token exchange failed: ${error} What it means
Thrown by KiroService.exchangeSocialCode when the Kiro auth service (https://prod.us-east-1.auth.desktop.kiro.dev/oauth/token) rejects the social-login authorization-code exchange with a non-2xx status; the upstream body is embedded in the message. The code+PKCE verifier could not be converted into access/refresh tokens, so Google/GitHub social login fails at the final step.
Source
Thrown at src/lib/oauth/services/kiro.js:162
async exchangeSocialCode(code, codeVerifier) {
// Must match the redirect_uri used in buildSocialLoginUrl
const redirectUri = "kiro://kiro.kiroAgent/authenticate-success";
const response = await fetch(`${KIRO_AUTH_SERVICE}/oauth/token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
code,
code_verifier: codeVerifier,
redirect_uri: redirectUri,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Token exchange failed: ${error}`);
}
const data = await response.json();
return {
accessToken: data.accessToken,
refreshToken: data.refreshToken,
profileArn: data.profileArn,
expiresIn: data.expiresIn || 3600,
};
}
/**
* Refresh token using refresh token
*/
async refreshToken(refreshToken, providerSpecificData = {}) {
const { authMethod, clientId, clientSecret, region } = providerSpecificData;
// AWS SSO OIDC refresh (Builder ID or IDC)View on GitHub (pinned to 90b52e06ff)
Solutions
- Restart the social login flow (new buildSocialLoginUrl → new code + verifier) and complete the exchange immediately; authorization codes are short-lived and single-use.
- Never modify the redirect_uri — it must stay kiro://kiro.kiroAgent/authenticate-success to match the authorization request.
- Ensure the full code string from the callback is passed without truncation/URL-encoding damage.
- Read the embedded upstream body for the exact OAuth error (invalid_grant, invalid_request, etc.).
Example fix
// before: retrying the same (already used) code
try { await svc.exchangeSocialCode(code, verifier); } catch { await svc.exchangeSocialCode(code, verifier); }
// after: restart the flow to get a fresh code/verifier
const { url, codeChallenge, state } = newFlow();
const { code, verifier } = await completeLogin(url, state);
await svc.exchangeSocialCode(code, verifier); Defensive patterns
Strategy: try-catch
Validate before calling
function canExchange(code, verifier) {
return typeof code === 'string' && code.length > 10 &&
typeof verifier === 'string' && verifier.length >= 43; // PKCE S256 verifier min length
}
if (!canExchange(code, codeVerifier)) throw new Error('Missing or malformed code/verifier — restart the social login'); Type guard
function isTokenPair(d) { return typeof d?.accessToken === 'string' && typeof d?.refreshToken === 'string'; } Try / catch
try {
return await svc.exchangeSocialCode(code, verifier);
} catch (e) {
if (/invalid_grant|expired/i.test(e.message)) {
return startFreshSocialLogin(); // codes are single-use — never retry the same code
}
throw e;
} Prevention
- Never retry an exchange with the same authorization code — it is single-use and short-lived.
- Never alter redirect_uri; it must remain kiro://kiro.kiroAgent/authenticate-success exactly as in buildSocialLoginUrl.
- Pass the code_verifier that generated the code_challenge for this specific state, not a stale one.
- Complete the exchange immediately after the callback to stay inside the code's lifetime.
When it happens
Trigger: POST to /oauth/token with { code, code_verifier, redirect_uri } returns !response.ok — expired or already-consumed authorization code, wrong code_verifier (PKCE mismatch), or redirect_uri not matching the one used in buildSocialLoginUrl.
Common situations: User took too long between the browser login and the manual callback paste; the callback URL was edited or truncated so the code doesn't match the verifier; retrying with a one-time-use code; Cognito rejecting because redirect_uri differs from the whitelisted kiro:// URI.
Related errors
- `Token exchange failed: ${error}`
- `Token exchange failed: ${error}`
- xAI token exchange failed: ${error}
- Token exchange failed: ${error}
- Token exchange failed: ${error}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/f346de3314660293.
Report an issue: GitHub.