Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion plugins/workos/skills/workos/references/workos-authkit-nextjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<Link>` prefetch can trigger it on hover (logging users out unexpectedly), and it is CSRF-exposable via `<img src="/auth/signout">`. `workos doctor` flags this as `SIGNOUT_GET_HANDLER`.

Use a **POST server action**. In a Server Component, an inline action is fine:

```tsx
<form action={async () => { 'use server'; await signOut(); }}>
<button type="submit">Sign out</button>
</form>
```
Comment on lines +209 to +213

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing import in server-component snippet

The inline-action code block calls signOut() without showing its import. An LLM installer agent (or a developer) copying only this block would produce ReferenceError: signOut is not defined. The import is shown later in the client-component section (app/auth/actions.ts) but not here. Adding import { signOut } from '@workos-inc/authkit-nextjs'; at the top of the snippet (or in a preceding comment) would make the example self-contained.


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';
// ...
<form action={signOutAction}>
<button type="submit">Sign out</button>
</form>
```

`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.).
Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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 `<form method="GET">`. A `GET` with a side effect is unsafe — Next.js prefetch and CSRF (`<img src="/auth/signout">`) 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 `<form>` uses a server-action `action={...}` (or `method="POST"`), not `method="GET"`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The parenthetical (or method="POST") is misleading: steps 1 and 2 already say to move sign-out to a server action and delete the GET route, so at this point the form's action should point to the server-action function — there is no remaining GET route to flip to POST. Leaving the hint in implies that simply changing the method attribute on the form is an acceptable alternative, but without a corresponding POST handler the form would 405. Removing the parenthetical keeps the fix unambiguous.

Suggested change
3. Ensure the sign-out `<form>` uses a server-action `action={...}` (or `method="POST"`), not `method="GET"`.
3. Ensure the sign-out `<form>` uses a server-action `action={...}`, 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/`
Expand Down