Repository navigation
typed API client ships no JWT auth wiring: every generated route that requires a valid JWT token returns 401 #433
Description
Problem
The frontend's typed API client is built for a header the backend no longer uses. api-client.ts attaches no auth header and still documents API keys:
// app/frontend/src/lib/api-client.ts
/**
* - Auth: the backend uses x-api-key headers. If a default key is needed,
* add `headers: { 'x-api-key': '...' }` to the createClient options.
*/
export const apiClient = createClient<paths>({
baseUrl: apiUrl,
fetch: withTimeoutFetch(fetchClient as typeof fetch) as typeof fetch,
});But the generated spec this client is typed against declares JWT auth on its routes:
// app/frontend/src/lib/generated/api.ts (multiple endpoints)
/** @description Unauthorized - valid JWT token required. */
The backend issued OIDC JWTs (tracked as issue #218), so the live API now expects a bearer token, while the generated client neither acquires, attaches, nor refreshes one.
Consequence: every request through apiClient to a JWT-required endpoint is sent without an Authorization header and fails with 401 unless each caller hand-rolls token attachment. Auth is fragmented: there is no single place that acquires a token, attaches it to openapi-fetch requests, and refreshes it on expiry, so the typed client's compile-time guarantees mask a runtime auth failure. The x-api-key comment actively misleads a contributor into wiring the wrong credential.
Root cause
The OIDC/JWT backend work (#218) changed the auth model after api-client.ts was written, but the client was never updated to a token-aware fetch, and no CI/test exercises an authenticated request end-to-end.
Why this is architecturally hard
- Token lifecycle is a cross-cutting concern. The fix is not a one-line header: it needs token acquisition, storage, attach-on-request, and refresh-on-401 (with single-flight refresh so concurrent requests share one refresh). A naive
headers: { Authorization: token }baked at client construction breaks the moment the token rotates. - Mock and SSR paths must stay coherent.
api-client.tsroutes throughfetchClient(mock interception viaNEXT_PUBLIC_USE_MOCKS) andwithTimeoutFetch; token attachment must compose with both, and with Next.js server/client boundaries (next.config.ts,src/proxy.ts,src/i18n.ts) rather than bypassing them. - The generated types are the source of truth. The client types come from
openapi.jsonviapnpm generate:api; the auth wiring must not fight the generatedpathstypes, and adding auth headers must keep the typed request/response contract intact. - Refresh failure must be observable. A 401 caused by an expired token must be distinguishable from a 401 caused by an unauthorized role so the client refreshes the former and surfaces the latter.
Proposed design
Introduce a token-aware fetch wrapper (or openapi-fetch middleware) that reads the token from a store, attaches Authorization: Bearer <token>, and performs single-flight refresh on 401. Keep createClient typed against paths, and update the x-api-key comment to reflect the JWT model.
Downstream impact
apiClient is the entry point for all frontend API calls (including verification-api.ts, verification-inbox-api.ts, and dashboard hooks under src/hooks). The auth model must stay consistent with app/backend/src/auth-oidc (JWT issuance/revocation). Regenerating types after any schema change is cd app/frontend && pnpm generate:api.
Acceptance criteria
Client
-
apiClientattaches a valid bearer token to authenticated requests without per-caller manual headers. - On 401 with an expired token, the client refreshes once (single-flight) and retries; a second 401 is surfaced as an auth error.
- The
x-api-keycomment is removed/replaced with the correct JWT guidance.
Tests
- A test proves authenticated requests carry the bearer header and that a 401 triggers a single refresh+retry.
Out of scope
The OpenAPI spec-drift CI gate and backend JWT issuance/revocation are separate issues.
Getting started
Files: app/frontend/src/lib/api-client.ts, app/frontend/src/lib/generated/api.ts, app/frontend/src/lib/env.ts, app/frontend/src/lib/retry.ts.
cd app/frontend
pnpm type-check
pnpm testGood first files to read: lib/api-client.ts (the client + stale comment) and a few 401 ... valid JWT token required entries in lib/generated/api.ts.
Activity
Opulencechuks commentedon Aug 19, 2026
I am confident that I can implement this issue having worked on similar issues in the past.
TomikeDS commentedon Aug 20, 2026
can i solve this?
grantfox-oss commentedon Aug 20, 2026
🦊 GrantFox — @TomikeDS has been assigned to this issue as part of the Third Campaign campaign!
Next steps:
Closes #433)Good luck! Track your progress on GrantFox.
grantfox-oss commentedon Aug 20, 2026
🎉 This issue has been marked as completed on GrantFox as part of the Third Campaign campaign!
@TomikeDS's PR #439 was approved and merged by @kilodesodiq-arch.
🏆 @TomikeDS: You earned 35 FoxPoints for this contribution! Your current tier: Explorer (920 total points). Track your full progress on GrantFox.
👏 Great work, @TomikeDS! Keep contributing to ChainForgee.
fix: wire JWT bearer auth into the typed API client (ChainForgee#439)