From 82361a8b8fea87b1797b4002d1c13d70d69242e2 Mon Sep 17 00:00:00 2001 From: Nick Nisi Date: Mon, 8 Jun 2026 15:46:57 -0500 Subject: [PATCH] fix(workos): mandate POST server-action sign-out in Next.js skill Sign-out mutates state, so a GET route handler is unsafe (CSRF + Next.js prefetch can trigger logout). The Next.js reference now documents a POST server-action pattern, a SIGNOUT_GET_HANDLER troubleshooting entry, and a verification-step grep that fails if a GET sign-out route is generated. This is the source-side fix for the installer scaffolding an insecure GET sign-out (workos doctor flags it as SIGNOUT_GET_HANDLER). --- .../references/workos-authkit-nextjs.md | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/plugins/workos/skills/workos/references/workos-authkit-nextjs.md b/plugins/workos/skills/workos/references/workos-authkit-nextjs.md index f13f63c..3c8dd9c 100644 --- a/plugins/workos/skills/workos/references/workos-authkit-nextjs.md +++ b/plugins/workos/skills/workos/references/workos-authkit-nextjs.md @@ -200,6 +200,40 @@ export function NavAuth() { Call `getSignInUrl()` **only** inside a Server Action or Route Handler. It is the safe wrapper for AuthKit sign-in/sign-up flows. +### Sign out with a POST server action, never a GET route + +Sign-out **mutates state** — it clears the session — so it must never be a `GET` route handler. A `GET /auth/signout` is unsafe: Next.js `` prefetch can trigger it on hover (logging users out unexpectedly), and it is CSRF-exposable via ``. `workos doctor` flags this as `SIGNOUT_GET_HANDLER`. + +Use a **POST server action**. In a Server Component, an inline action is fine: + +```tsx +
{ 'use server'; await signOut(); }}> + +
+``` + +A **client** component (for example a nav that needs `useAuth()`) cannot define an inline `'use server'` action — put it in a separate server-action module and import it: + +```tsx +// app/auth/actions.ts +'use server'; +import { signOut } from '@workos-inc/authkit-nextjs'; // verify export path in README +export async function signOutAction() { + await signOut(); +} +``` + +```tsx +'use client'; +import { signOutAction } from '@/app/auth/actions'; +// ... +
+ +
+``` + +`signOut()` accepts an optional `{ returnTo }`; with none, it redirects to the Logout URI configured in your WorkOS dashboard. If a generated `GET` sign-out route exists, **delete it** rather than switching it to `POST` — that removes the extra logout surface entirely. + ### Critical auth URL gotchas - **Never** call `getSignInUrl()` / `getSignUpUrl()` inside a Server Component render (`page.tsx`, `layout.tsx`, async `nav-auth.tsx`, etc.). @@ -230,7 +264,10 @@ rg -n "getAuthorizationUrl|window\.location\.href\s*=\s*auth\.signInUrl" app src # 5. Audit getSignInUrl() usage — safe in Server Actions/Route Handlers, unsafe in page/layout/component render rg -n "getSignInUrl\(" app src/app 2>/dev/null || true -# 6. Build succeeds +# 6. CRITICAL: Audit for an unsafe GET sign-out route — sign-out mutates state, so it must be a POST server action, never a GET handler +rg -n -g '**/{signout,sign-out,logout}/route.*' "export (async )?function GET|export const GET" app src/app 2>/dev/null && echo "FAIL: sign-out is a GET route — convert to a POST server action and delete the GET route (see 'Sign out with a POST server action, never a GET route')" || echo "OK: no GET sign-out route" + +# 7. Build succeeds npm run build ``` @@ -289,6 +326,17 @@ This error causes OAuth codes to expire ("invalid_grant"), so fix the handler fi 2. For client-side sign-in buttons, use `refreshAuth({ ensureSignedIn: true })` 3. Do not hand-roll the sign-in action with raw `getAuthorizationUrl()` unless you also persist `sealedState` exactly as the SDK expects +### `SIGNOUT_GET_HANDLER` (flagged by `workos doctor`) + +**Cause:** The sign-out route is a `GET` handler (e.g. `export async function GET() { return signOut(); }`), often paired with a `
`. A `GET` with a side effect is unsafe — Next.js prefetch and CSRF (``) can trigger logout. + +**Fix:** + +1. Move sign-out to a POST server action (see "Sign out with a POST server action, never a GET route" above). +2. Delete the `GET` sign-out route entirely. +3. Ensure the sign-out `` uses a server-action `action={...}` (or `method="POST"`), not `method="GET"`. +4. Re-run `workos doctor` to confirm the finding clears. + ### "middleware.ts not found" - Check: File at project root or `src/`, not inside `app/`