nexu-io/open-design · error · Error

token endpoint rejected request: HTTP ${res.status} ${res.st

Error message

token endpoint rejected request: HTTP ${res.status} ${res.statusText} ${txt}

What it means

exchangeCodeForToken POSTs the authorization code plus PKCE verifier to the token endpoint. A non-2xx response fails with status, statusText, and up to 500 chars of body. Common reasons: expired or already-used auth code, wrong redirectUri, bad client_secret, PKCE mismatch, or invalid_grant.

Source

Thrown at apps/daemon/src/mcp-oauth.ts:424

  const headers: Record<string, string> = {
    'content-type': 'application/x-www-form-urlencoded',
    accept: 'application/json',
  };
  if (clientSecret) {
    // RFC 6749 §2.3.1 — confidential clients use HTTP Basic with the
    // client_id we already put in the form. Public clients (PKCE-only)
    // skip this branch.
    const basic = Buffer.from(`${form.get('client_id')}:${clientSecret}`).toString('base64');
    headers['authorization'] = `Basic ${basic}`;
  }
  const res = await fetchImpl(tokenEndpoint, {
    method: 'POST',
    headers,
    body: form.toString(),
  });
  if (!res.ok) {
    const txt = await safeText(res);
    throw new Error(
      `token endpoint rejected request: HTTP ${res.status} ${res.statusText} ${txt}`,
    );
  }
  const json = (await res.json()) as OAuthTokenResponse;
  if (!json.access_token) {
    throw new Error('token endpoint response missing access_token');
  }
  return json;
}

async function safeText(res: Response): Promise<string> {
  try {
    const t = await res.text();
    return t.slice(0, 500);
  } catch {
    return '';
  }
}

View on GitHub (pinned to 5be4028344)

Solutions

  1. Read the status and body in the message to distinguish invalid_grant from auth failure.
  2. Ensure the auth code is exchanged exactly once and promptly.
  3. Keep the same redirectUri and PKCE verifier used in beginAuth.
  4. For confidential clients, confirm client_secret.
Defensive patterns

Strategy: retry

Try / catch

try {
  await exchangeCodeForToken(tokenEndpoint, params);
} catch (e) {
  if (/token endpoint rejected/i.test(e.message)) {
    // a replayed or expired code yields invalid_grant -> restart the flow
    if (/invalid_grant/i.test(e.message)) return beginAuth(input);
  }
  throw e;
}

Prevention

When it happens

Trigger: Replaying a single-use auth code; code expired; redirectUri in the token request differs from the authorize step; PKCE verifier mismatch; confidential client with the wrong secret.

Common situations: The OAuth callback was hit twice (retry or double-click); clock skew; the verifier was not preserved between beginAuth and the callback.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/cff65e217018390b. Report an issue: GitHub.