From 2ae6804cc424cd0769cb425afbabd54fe0ce9273 Mon Sep 17 00:00:00 2001 From: Petru Paler Date: Mon, 12 Jan 2026 13:59:53 +0000 Subject: [PATCH] Fix OAuth login by syncing auth state to cookie Root cause: PocketBase SDK stores auth in localStorage, but Next.js middleware checks for pb_auth cookie. The cookie was never being set after successful OAuth login. Fix: Add pb.authStore.onChange() listener that syncs auth state to cookie on any change (login, logout, token refresh). This is the idiomatic PocketBase pattern for Next.js SSR apps. Also updates authentication spec to reflect that the cookie is non-HttpOnly by design (client SDK needs read/write access). Co-Authored-By: Claude Opus 4.5 --- specs/authentication.md | 8 +++++--- src/app/login/page.tsx | 13 +------------ src/lib/pocketbase.ts | 9 +++++++++ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/specs/authentication.md b/specs/authentication.md index ae8c77f..6940440 100644 --- a/specs/authentication.md +++ b/specs/authentication.md @@ -41,8 +41,10 @@ POCKETBASE_OIDC_ISSUER_URL=https://id.yourdomain.com ### Session Management -- PocketBase manages session tokens automatically -- Auth state persisted in browser (cookie/localStorage) +- PocketBase SDK stores auth in localStorage by default +- `pb.authStore.onChange()` listener syncs auth state to `pb_auth` cookie +- Cookie enables Next.js middleware to check auth server-side +- Cookie is non-HttpOnly (PocketBase SDK requires client-side access) - Session expires after 14 days of inactivity ## Pages @@ -159,7 +161,7 @@ User profile management: - [ ] GET `/api/user` returns current user data - [ ] PATCH `/api/user` updates user record - [ ] Logout clears session completely -- [ ] Auth cookie is HttpOnly and Secure +- [ ] Auth cookie syncs via onChange listener (non-HttpOnly per PocketBase design) ## Future Enhancements diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index c8c59d8..7a715bc 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -120,22 +120,11 @@ export default function LoginPage() { setError(null); try { - console.log("[OAuth] Starting authWithOAuth2..."); - const result = await pb - .collection("users") - .authWithOAuth2({ provider: "oidc" }); - console.log("[OAuth] authWithOAuth2 resolved:", result); - console.log("[OAuth] authStore.isValid:", pb.authStore?.isValid); - console.log( - "[OAuth] authStore.token:", - `${pb.authStore?.token?.substring(0, 20)}...`, - ); + await pb.collection("users").authWithOAuth2({ provider: "oidc" }); // Reset attempts on successful login setLoginAttempts([]); - console.log("[OAuth] Calling router.push..."); router.push("/"); } catch (err) { - console.error("[OAuth] Error caught:", err); // Record the failed attempt recordAttempt(); const message = diff --git a/src/lib/pocketbase.ts b/src/lib/pocketbase.ts index 1050e02..20503a0 100644 --- a/src/lib/pocketbase.ts +++ b/src/lib/pocketbase.ts @@ -21,6 +21,15 @@ export const pb = new PocketBase(POCKETBASE_URL); // Disable auto-cancellation for server-side usage pb.autoCancellation(false); +// Sync auth state to cookie on any change (login, logout, token refresh) +// This allows Next.js middleware to read auth state for route protection +// Cookie is non-HttpOnly because the client-side SDK needs read/write access +if (typeof window !== "undefined") { + pb.authStore.onChange(() => { + document.cookie = pb.authStore.exportToCookie({ httpOnly: false }); + }); +} + /** * Creates a new PocketBase client instance. * Use this in server components and API routes to get a fresh client