From 360b611ae65dea6d93c589ad8792a0a244e3abd5 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 08:48:50 +0200 Subject: [PATCH] fix(auth): default signInPath to /api/auth/signin (NextAuth v5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provider-specific paths like /api/auth/signin/keycloak are POST only in NextAuth v5 — they're the form-submit endpoint with CSRF. A GET redirect there bounces to /api/auth/error?error=Configuration with "UnknownAction". /api/auth/signin (no provider segment) is the GET-accessible page that lists configured providers. Apps that want one-click Keycloak should set signInPath to a custom page that calls signIn('keycloak'). Repros against next-auth 5.0.0-beta.31 on Next 16.1.1. Pre-existing bug in createAuth + createAuthMiddleware + signInRedirect; surfaced when first user-driven login was attempted against the live CRM. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/auth/index.ts | 4 +++- src/auth/middleware.ts | 4 +++- src/auth/server.ts | 8 +++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/auth/index.ts b/src/auth/index.ts index 21df4af..b2b7ee7 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -17,7 +17,9 @@ export type { SessionUser } from "./types"; */ export function signInRedirect(callbackUrl?: string): void { if (typeof window === "undefined") return; - const target = "/api/auth/signin/keycloak"; + // Match the server-side default. /api/auth/signin/ is POST + // only in NextAuth v5 — a GET navigation there returns UnknownAction. + const target = "/api/auth/signin"; const url = new URL(target, window.location.origin); url.searchParams.set("callbackUrl", callbackUrl ?? window.location.pathname); window.location.href = url.toString(); diff --git a/src/auth/middleware.ts b/src/auth/middleware.ts index 9f5e888..78367da 100644 --- a/src/auth/middleware.ts +++ b/src/auth/middleware.ts @@ -48,7 +48,9 @@ export interface AuthMiddlewareOptions { */ export function createAuthMiddleware(opts: AuthMiddlewareOptions = {}) { const publicRoutes = opts.publicRoutes ?? []; - const signInPath = opts.signInPath ?? "/api/auth/signin/keycloak"; + // See createAuth for why /api/auth/signin (not /api/auth/signin/keycloak): + // provider-specific paths are POST-only in NextAuth v5. + const signInPath = opts.signInPath ?? "/api/auth/signin"; return function middleware(req: NextRequestLike) { const { pathname } = req.nextUrl; diff --git a/src/auth/server.ts b/src/auth/server.ts index 4d7556f..8a88aa8 100644 --- a/src/auth/server.ts +++ b/src/auth/server.ts @@ -63,7 +63,13 @@ export interface AuthBundle { * }); */ export function createAuth(opts: CreateAuthOptions): AuthBundle { - const signInPath = opts.signInPath ?? "/api/auth/signin/keycloak"; + // NextAuth v5: provider-specific paths like /api/auth/signin/keycloak + // are POST-only (CSRF-protected form submit). A GET redirect there + // bounces to /api/auth/error?error=Configuration ("UnknownAction"). + // /api/auth/signin (no provider) is the GET-accessible page that + // lists configured providers. Apps wanting one-click Keycloak can + // override signInPath with a custom page that calls signIn('keycloak'). + const signInPath = opts.signInPath ?? "/api/auth/signin"; const defaultTenantId = opts.defaultTenantId ?? "00000000-0000-0000-0000-000000000000";