Fix OAuth login by syncing auth state to cookie
All checks were successful
Deploy / deploy (push) Successful in 1m37s

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 <noreply@anthropic.com>
This commit is contained in:
2026-01-12 13:59:53 +00:00
parent e2afee2045
commit 2ae6804cc4
3 changed files with 15 additions and 15 deletions

View File

@@ -41,8 +41,10 @@ POCKETBASE_OIDC_ISSUER_URL=https://id.yourdomain.com
### Session Management ### Session Management
- PocketBase manages session tokens automatically - PocketBase SDK stores auth in localStorage by default
- Auth state persisted in browser (cookie/localStorage) - `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 - Session expires after 14 days of inactivity
## Pages ## Pages
@@ -159,7 +161,7 @@ User profile management:
- [ ] GET `/api/user` returns current user data - [ ] GET `/api/user` returns current user data
- [ ] PATCH `/api/user` updates user record - [ ] PATCH `/api/user` updates user record
- [ ] Logout clears session completely - [ ] Logout clears session completely
- [ ] Auth cookie is HttpOnly and Secure - [ ] Auth cookie syncs via onChange listener (non-HttpOnly per PocketBase design)
## Future Enhancements ## Future Enhancements

View File

@@ -120,22 +120,11 @@ export default function LoginPage() {
setError(null); setError(null);
try { try {
console.log("[OAuth] Starting authWithOAuth2..."); await pb.collection("users").authWithOAuth2({ provider: "oidc" });
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)}...`,
);
// Reset attempts on successful login // Reset attempts on successful login
setLoginAttempts([]); setLoginAttempts([]);
console.log("[OAuth] Calling router.push...");
router.push("/"); router.push("/");
} catch (err) { } catch (err) {
console.error("[OAuth] Error caught:", err);
// Record the failed attempt // Record the failed attempt
recordAttempt(); recordAttempt();
const message = const message =

View File

@@ -21,6 +21,15 @@ export const pb = new PocketBase(POCKETBASE_URL);
// Disable auto-cancellation for server-side usage // Disable auto-cancellation for server-side usage
pb.autoCancellation(false); 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. * Creates a new PocketBase client instance.
* Use this in server components and API routes to get a fresh client * Use this in server components and API routes to get a fresh client