anomalyco/sst · error

Invalid grant_type

Error message

Invalid grant_type

What it means

SST's AuthHandler /token endpoint only implements the authorization_code grant. If the posted form's grant_type is anything other than "authorization_code", it returns HTTP 400 with this message. It guards the token exchange endpoint against unsupported or malformed token requests.

Source

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

        Object.fromEntries((response.headers as any).entries())
      );
    },
    cookie(c, key, value, maxAge) {
      setCookie(c, key, value, {
        maxAge,
        httpOnly: true,
        ...(c.req.url.startsWith("https://")
          ? { secure: true, sameSite: "None" }
          : {}),
      });
    },
  };

  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);

View on GitHub (pinned to a0bd20f762)

Solutions

  1. Send grant_type=authorization_code in the /token form body for the initial code exchange.
  2. If you need refresh tokens, implement refresh handling yourself or use a provider that supports it — this handler does not.
  3. Remove/replace client libraries that default to other grant types when talking to this endpoint.
  4. Confirm the request is form-encoded (application/x-www-form-urlencoded) with grant_type present, matching what the authorize step returned.

Example fix

// before
await fetch(`${authUrl}/token`, { method: "POST", body: new URLSearchParams({ grant_type: "refresh_token", refresh_token }) });

// after
await fetch(`${authUrl}/token`, {
  method: "POST",
  body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: cb, client_id: id }),
});
Defensive patterns

Strategy: validation

Validate before calling

// validate the token request body before sending
const body = new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri, client_id });
if (body.get("grant_type") !== "authorization_code") throw new Error("Only authorization_code grant is supported");

Type guard

function isAuthorizationCodeGrant(form: URLSearchParams): boolean {
  return form.get("grant_type") === "authorization_code";
}

Try / catch

const res = await fetch(`${authUrl}/token`, { method: "POST", body });
if (res.status === 400) {
  const msg = await res.text();
  if (msg === "Invalid grant_type") throw new Error("Set grant_type=authorization_code");
}

Prevention

When it happens

Trigger: POSTing to /token with form field grant_type set to refresh_token, password, client_credentials, or missing entirely.

Common situations: A token client that auto-refreshes using grant_type=refresh_token, which this handler doesn't support; an OAuth client library sending client_credentials for machine flows; omitting grant_type in a hand-rolled token request; pointing a generic OIDC client at this endpoint.

Related errors


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