fix: cache Convex JWT in server auth

This commit is contained in:
Matthias
2026-06-05 17:04:03 +02:00
parent 5a42c637c6
commit 5352893a47
3 changed files with 83 additions and 8 deletions

View File

@@ -1,10 +1,10 @@
--- ---
id: TASK-28 id: TASK-28
title: Diagnose dashboard initial-load retry loop title: Diagnose dashboard initial-load retry loop
status: In Progress status: Done
assignee: [] assignee: []
created_date: '2026-06-05 13:46' created_date: '2026-06-05 13:46'
updated_date: '2026-06-05 14:01' updated_date: '2026-06-05 15:03'
labels: [] labels: []
dependencies: [] dependencies: []
priority: high priority: high
@@ -27,12 +27,11 @@ Find the root cause of the repeated dashboard requests on initial load, especial
## Implementation Plan ## Implementation Plan
<!-- SECTION:PLAN:BEGIN --> <!-- SECTION:PLAN:BEGIN -->
1. Read provided logs and identify repeated route pattern 1. Add a focused regression test proving server auth config enables Convex JWT cookie reuse.
2. Trace dashboard auth, routing, and navigation layers 2. Verify the test fails against the current auth-server configuration.
3. Reproduce the repeated requests locally or via tests 3. Enable the documented jwtCache option in convexBetterAuthNextJs with a scoped auth-error predicate.
4. Confirm the root cause with the smallest evidence-producing change 4. Run the focused test, full test suite, and lint.
5. Implement one targeted fix 5. Record verification and leave TASK-28 open for user Firefox/Zen confirmation.
6. Run focused verification and update acceptance criteria
<!-- SECTION:PLAN:END --> <!-- SECTION:PLAN:END -->
## Implementation Notes ## Implementation Notes
@@ -72,4 +71,28 @@ Verification:
- node --test .test-output/tests/dashboard-prefetch.test.js .test-output/tests/dashboard-theme.test.js passes. - node --test .test-output/tests/dashboard-prefetch.test.js .test-output/tests/dashboard-theme.test.js passes.
- pnpm test passes 260/260. - pnpm test passes 260/260.
- pnpm lint passes with existing generated/unused warnings only, no errors. - 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 --> <!-- 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 -->

View File

@@ -1,5 +1,30 @@
import { convexBetterAuthNextJs } from "@convex-dev/better-auth/nextjs"; 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 { export const {
handler, handler,
preloadAuthQuery, preloadAuthQuery,
@@ -11,4 +36,8 @@ export const {
} = convexBetterAuthNextJs({ } = convexBetterAuthNextJs({
convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!, convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,
convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!, convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
jwtCache: {
enabled: true,
isAuthError: isConvexAuthError,
},
}); });

View 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/);
});