fix: cache Convex JWT in server auth
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
---
|
||||
id: TASK-28
|
||||
title: Diagnose dashboard initial-load retry loop
|
||||
status: In Progress
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-06-05 13:46'
|
||||
updated_date: '2026-06-05 14:01'
|
||||
updated_date: '2026-06-05 15:03'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: high
|
||||
@@ -27,12 +27,11 @@ Find the root cause of the repeated dashboard requests on initial load, especial
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Read provided logs and identify repeated route pattern
|
||||
2. Trace dashboard auth, routing, and navigation layers
|
||||
3. Reproduce the repeated requests locally or via tests
|
||||
4. Confirm the root cause with the smallest evidence-producing change
|
||||
5. Implement one targeted fix
|
||||
6. Run focused verification and update acceptance criteria
|
||||
1. Add a focused regression test proving server auth config enables Convex JWT cookie reuse.
|
||||
2. Verify the test fails against the current auth-server configuration.
|
||||
3. Enable the documented jwtCache option in convexBetterAuthNextJs with a scoped auth-error predicate.
|
||||
4. Run the focused test, full test suite, and lint.
|
||||
5. Record verification and leave TASK-28 open for user Firefox/Zen confirmation.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
@@ -72,4 +71,28 @@ Verification:
|
||||
- node --test .test-output/tests/dashboard-prefetch.test.js .test-output/tests/dashboard-theme.test.js passes.
|
||||
- pnpm test passes 260/260.
|
||||
- pnpm lint passes with existing generated/unused warnings only, no errors.
|
||||
|
||||
2026-06-05 Firefox/Zen HAR follow-up:
|
||||
- User confirmed the reload loop reproduces in Firefox/Zen but not Chrome.
|
||||
- HAR shows repeated top-level document navigations to /dashboard/audits, not XHR retries or Link prefetch.
|
||||
- Requests already include better-auth.convex_jwt, but SSR responses embed fresh initialToken values and /api/auth/convex/token later sets better-auth.convex_jwt again.
|
||||
- Local @convex-dev/better-auth source shows getToken() fetches /convex/token unless jwtCache.enabled is configured.
|
||||
Next implementation hypothesis: enable jwtCache so server getToken() reuses a valid Convex JWT cookie instead of minting a new token during each root layout render.
|
||||
|
||||
Implemented Firefox/Zen token-churn fix:
|
||||
- Added jwtCache.enabled to lib/auth-server.ts for convexBetterAuthNextJs, matching the Convex Better Auth Next.js server utilities docs.
|
||||
- Added a scoped isConvexAuthError predicate so recognized application auth failures still surface, while stale cached-token failures can trigger the library refresh path.
|
||||
- Added tests/auth-server-jwt-cache.test.ts to guard the server auth cache configuration.
|
||||
Verification after fix:
|
||||
- pnpm exec tsc -p tsconfig.test.json passes.
|
||||
- node --test .test-output/tests/auth-server-jwt-cache.test.js passes after failing before the implementation.
|
||||
- pnpm test passes 265/265.
|
||||
- pnpm lint passes with two existing generated-file warnings and no errors.
|
||||
Manual confirmation still needed in Firefox/Zen before closing TASK-28 as Done.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Firefox/Zen reload loop fixed by enabling Convex Better Auth JWT caching in Next.js server auth utilities; regression test added and full tests/lint passed. User confirmed dashboard now loads reliably without loops.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
import { convexBetterAuthNextJs } from "@convex-dev/better-auth/nextjs";
|
||||
|
||||
function getErrorText(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
if (typeof error === "string") {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (error && typeof error === "object") {
|
||||
const { data, message } = error as { data?: unknown; message?: unknown };
|
||||
return [message, data]
|
||||
.filter((value): value is string => typeof value === "string")
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function isConvexAuthError(error: unknown): boolean {
|
||||
return /\b(Unauthenticated|Unauthorized|Not authenticated)\b/i.test(
|
||||
getErrorText(error),
|
||||
);
|
||||
}
|
||||
|
||||
export const {
|
||||
handler,
|
||||
preloadAuthQuery,
|
||||
@@ -11,4 +36,8 @@ export const {
|
||||
} = convexBetterAuthNextJs({
|
||||
convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,
|
||||
convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
|
||||
jwtCache: {
|
||||
enabled: true,
|
||||
isAuthError: isConvexAuthError,
|
||||
},
|
||||
});
|
||||
|
||||
23
tests/auth-server-jwt-cache.test.ts
Normal file
23
tests/auth-server-jwt-cache.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
const authServerPath = join(process.cwd(), "lib", "auth-server.ts");
|
||||
|
||||
test("server auth utilities reuse valid Convex JWT cookies", async () => {
|
||||
const source = await readFile(authServerPath, "utf8");
|
||||
|
||||
assert.match(source, /jwtCache:\s*\{/);
|
||||
assert.match(source, /enabled:\s*true/);
|
||||
assert.match(source, /isAuthError:\s*isConvexAuthError/);
|
||||
});
|
||||
|
||||
test("server auth error predicate keeps stale token refresh possible", async () => {
|
||||
const source = await readFile(authServerPath, "utf8");
|
||||
|
||||
assert.match(source, /function isConvexAuthError\(error: unknown\): boolean/);
|
||||
assert.match(source, /Unauthenticated/);
|
||||
assert.match(source, /Unauthorized/);
|
||||
assert.doesNotMatch(source, /InvalidToken/);
|
||||
});
|
||||
Reference in New Issue
Block a user