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
- Send the exact same redirect_uri string in the /token request as was used in /authorize (scheme, host, port, path).
- Normalize the callback URL construction so both requests derive it from one constant.
- Check for trailing-slash, localhost-vs-127.0.0.1, and proxy-rewritten host differences.
- 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
- Define redirect_uri once in a shared constant used by both authorize and token steps.
- Beware localhost vs 127.0.0.1, trailing slashes, and default-port differences.
- Don't let proxies rewrite the Host of the callback URL.
- The handler compares exact strings — byte-identical, not URL-equivalent.
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
- client_id mismatch
- Missing token parameter
- error
- Unsupported response_type: ${response_type}
- Invalid grant_type
AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30).
Data as JSON: /api/errors/e0fef8dfa8835823.
Report an issue: GitHub.