anomalyco/sst · error

redirect_uri mismatch

Error message

redirect_uri mismatch

What it means

The /token endpoint verifies the authorization-code JWT and compares its embedded redirect_uri claim against the redirect_uri in the token request form. On mismatch it returns HTTP 400 with "redirect_uri mismatch". This is a standard OAuth security check preventing code interception by swapping redirect targets.

Source

Thrown at sdk/js/src/auth/handler.ts:247

  app.post("/token", async (c) => {
    console.log("token request");
    const form = await c.req.formData();
    if (form.get("grant_type") !== "authorization_code") {
      c.status(400);
      return c.text("Invalid grant_type");
    }
    const code = form.get("code");
    if (!code) {
      c.status(400);
      return c.text("Missing code");
    }

    const { payload } = await jwtVerify(
      code as string,
      await options.signing.publicKey()
    );
    if (payload.redirect_uri !== form.get("redirect_uri")) {
      c.status(400);
      return c.text("redirect_uri mismatch");
    }
    if (payload.client_id !== form.get("client_id")) {
      c.status(400);
      return c.text("client_id mismatch");
    }

    return c.json({
      access_token: payload.token,
    });
  });

  app.use("/:provider/authorize", async (c, next) => {
    const provider = c.req.param("provider");
    console.log("authorize request for", provider);
    const response_type =
      c.req.query("response_type") || getCookie(c, "response_type");
    const redirect_uri =

View on GitHub (pinned to a0bd20f762)

Solutions

  1. Send the exact same redirect_uri string in the /token request as was used in /authorize (scheme, host, port, path).
  2. Normalize the callback URL construction so both requests derive it from one constant.
  3. Check for trailing-slash, localhost-vs-127.0.0.1, and proxy-rewritten host differences.
  4. Log both values (payload.redirect_uri from the decoded code vs the form field) to spot the exact difference.

Example fix

// before
// authorize: redirect_uri=http://localhost:3000/callback
// token:     redirect_uri=http://127.0.0.1:3000/callback/

// after
const REDIRECT_URI = "http://localhost:3000/callback"; // single constant
// authorize: ...&redirect_uri=${encodeURIComponent(REDIRECT_URI)}
// token: body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: REDIRECT_URI, client_id })
Defensive patterns

Strategy: validation

Validate before calling

// ensure identical redirect_uri in both steps
const REDIRECT_URI = "http://localhost:3000/callback";
if (new URL(callbackUrl).toString() !== new URL(REDIRECT_URI).toString()) {
  throw new Error("Callback URL diverged from REDIRECT_URI");
}

Type guard

function redirectMatches(authorizeUri: string, tokenUri: string): boolean {
  return authorizeUri === tokenUri; // handler uses exact string comparison
}

Try / catch

const res = await fetch(`${authUrl}/token`, { method: "POST", body });
if (res.status === 400) {
  const msg = await res.text();
  if (msg === "redirect_uri mismatch") {
    throw new Error("Use the exact redirect_uri from the /authorize request");
  }
}

Prevention

When it happens

Trigger: Exchanging a code at /token while posting a redirect_uri that differs from the one used in the original /authorize request — different scheme, host, path, trailing slash, or port.

Common situations: Using localhost during authorize but 127.0.0.1 at token exchange; adding/removing a trailing slash or port between the two requests; a client behind a proxy that rewrites the callback URL; forgetting to send redirect_uri at all in the token request.

Related errors


AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30). Data as JSON: /api/errors/e0fef8dfa8835823. Report an issue: GitHub.