{"record":{"id":"853ca532944bf0c1","repo":"nextauthjs/next-auth","slug":"only-get-and-post-requests-are-supported","errorCode":null,"errorMessage":"Only GET and POST requests are supported","messagePattern":"Only GET and POST requests are supported","errorType":"exception","errorClass":"UnknownAction","httpStatus":null,"severity":"error","filePath":"packages/core/src/lib/utils/web.ts","lineNumber":33,"sourceCode":"async function getBody(req: Request): Promise<Record<string, any> | undefined> {\n  if (!(\"body\" in req) || !req.body || req.method !== \"POST\") return\n\n  const contentType = req.headers.get(\"content-type\")\n  if (contentType?.includes(\"application/json\")) {\n    return await req.json()\n  } else if (contentType?.includes(\"application/x-www-form-urlencoded\")) {\n    const params = new URLSearchParams(await req.text())\n    return Object.fromEntries(params)\n  }\n}\n\nexport async function toInternalRequest(\n  req: Request,\n  config: AuthConfig\n): Promise<RequestInternal | undefined> {\n  try {\n    if (req.method !== \"GET\" && req.method !== \"POST\")\n      throw new UnknownAction(\"Only GET and POST requests are supported\")\n\n    // Defaults are usually set in the `init` function, but this is needed below\n    config.basePath ??= \"/auth\"\n\n    const url = new URL(req.url)\n\n    const { action, providerId } = parseActionAndProviderId(\n      url.pathname,\n      config.basePath\n    )\n\n    return {\n      url,\n      action,\n      providerId,\n      method: req.method,\n      headers: Object.fromEntries(req.headers),\n      body: req.body ? await getBody(req) : undefined,","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/nextauthjs/next-auth/blob/a1a16a5a7780488c7449feece410033f445d0b31/packages/core/src/lib/utils/web.ts#L15-L51","documentation":"toInternalRequest converts the framework's Request into an internal request and only supports GET and POST methods. Any other HTTP method (PUT, DELETE, PATCH, HEAD with body semantics, OPTIONS) is rejected with UnknownAction before routing.","triggerScenarios":"Sending DELETE /auth/session, PUT to a signin/callback route, or an OPTIONS preflight hitting the auth handler directly without being short-circuited by the server.","commonSituations":"REST clients calling the session endpoint with DELETE expecting it to sign out (use POST /auth/signout instead); misconfigured CORS middleware letting OPTIONS reach the auth route; frameworks wiring auth to methods other than GET/POST.","solutions":["Use only GET (e.g. session, csrf, providers) and POST (signin, signout, callback) requests against auth routes","Sign out with POST /auth/signout rather than DELETE /auth/session","Configure the server/CORS layer to handle OPTIONS preflights before they reach the auth handler"],"exampleFix":"// before\nawait fetch(\"/api/auth/session\", { method: \"DELETE\" }) // sign out\n// after\nawait fetch(\"/api/auth/signout\", { method: \"POST\" })","handlingStrategy":"try-catch","validationCode":"if (![\"GET\",\"POST\"].includes(request.method)) return new Response(\"Method Not Allowed\", { status: 405 });","typeGuard":"function isSupportedMethod(m: string): m is \"GET\"|\"POST\" { return m === \"GET\" || m === \"POST\"; }","tryCatchPattern":"try { return await auth(req) } catch (e) { if (String(e.message).includes(\"Only GET and POST\")) return new Response(\"Method Not Allowed\", { status: 405 }); throw e; }","preventionTips":["Use GET for reads (session, csrf, providers) and POST for mutations (signin, signout, callback)","Never issue DELETE/PUT against auth endpoints","Handle OPTIONS preflight in middleware before the auth handler"],"tags":["http-method","routing","unsupported-method"],"backgroundTag":"unsupported-http-method","analyzedSha":"a1a16a5a7780488c7449feece410033f445d0b31","analyzedAt":"2026-08-28T21:52:38.200Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}