Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8bb7f91
feat: add Fireproof Dashboard with tenant and ledger management using…
jchris Nov 25, 2025
9bb21f5
debug log
jchris Nov 26, 2025
41e6228
feat: Improve error message for JWKS token verification failures
jchris Dec 1, 2025
efcd5f4
feat: Parse and check token expiry before use
jchris Dec 1, 2025
6d7ab15
feat: Warn on token issuer mismatch
jchris Dec 1, 2025
d55e2a6
feat: Clarify backend config issue in warning
jchris Dec 1, 2025
2d42fef
feat: Update auth mismatch warning with backend type limitation
jchris Dec 1, 2025
107238c
fix: Remove misleading auth type mismatch warning
jchris Dec 1, 2025
d28f13f
feat: Implement fp-cloud-jwt token exchange for DashboardApi
jchris Dec 1, 2025
2de890c
fix: Correct DashboardApi initialization and token type for fp-cloud-jwt
jchris Dec 1, 2025
b2f74cc
fix: Wait for fp-cloud-jwt before fetching dashboard data
jchris Dec 1, 2025
0a29543
feat: Log token header and audience for debugging
jchris Dec 1, 2025
282a182
feat: Add explicit KID check for debugging
jchris Dec 1, 2025
f5693b1
docs: Add token auth exchange analysis for debugging
jchris Dec 1, 2025
4978636
feat: Enhance logging to indicate query enablement
jchris Dec 1, 2025
ef01ee3
chore: Apply formatting changes from pnpm check
jchris Dec 1, 2025
5947147
refactor: Revert auth exchange complexity and use Clerk token directly
jchris Dec 1, 2025
ff7144a
fix: Restore required fetch prop in DashboardApi config
jchris Dec 1, 2025
c50641f
fix: Attempt to set audience to 'fireproof' for Clerk token
jchris Dec 1, 2025
f20db4d
feat: Temporarily log Clerk JWT for debugging
jchris Dec 1, 2025
449b91a
chore: Remove temporary debug logging
jchris Dec 1, 2025
a9e8786
format
jchris Dec 1, 2025
6182f83
remove audience
jchris Dec 1, 2025
ff46a54
refactor: Simplify fireproof route to only call ensureUser
jchris Dec 1, 2025
9e4ddf0
fix: Remove unsupported 'audience' from Clerk getToken call
jchris Dec 1, 2025
3e7395d
fix: Remove unused useEffect import
jchris Dec 1, 2025
1c47ff1
fix: Update CONNECT_API_URL to handle production and development envi…
jchris Dec 1, 2025
fc2e6b4
fix: Address PR feedback for fireproof dashboard route
jchris Dec 1, 2025
b8f02af
fix: Wrap fetch in arrow function to preserve context
jchris Dec 1, 2025
ebd9e03
fix: Update FireproofDashboard to include userId in ensureUser query …
jchris Dec 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 81 additions & 0 deletions token-auth-and-exchange.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Token Auth & Exchange Flow Analysis

This document describes the current observed authentication flow and the specific failure point encountered when integrating the `vibes.diy` development environment with the Fireproof Connect backend.

## The Goal

Authenticate a user on the `vibes.diy` local development environment using a **Clerk Development Instance** (`sincere-cheetah-30...`) and successfully exchange this token for a Fireproof Cloud token (`fp-cloud-jwt`) via the production Fireproof backend (`https://connect.fireproof.direct/api`).

## The Flow

1. **Client Authentication (Success)**
- The `vibes.diy` app uses `@clerk/clerk-react` to sign the user in.
- **Result:** A valid JWT is obtained.
- **Issuer (`iss`):** `https://sincere-cheetah-30.clerk.accounts.dev`
- **Key ID (`kid`):** `ins_35qNS5Jwyc7z4aJRBIS7o205yzb`
- **Algorithm:** `RS256`
- **Audience (`aud`):** `undefined` (No audience claim present).

2. **Token Verification / Exchange Request (Failure)**
- The client calls `DashboardApi.getCloudSessionToken({})`.
- **Payload:** `{ auth: { type: "clerk", token: "..." } }`
- **Endpoint:** `PUT https://connect.fireproof.direct/api`

3. **Backend Verification (Error)**
_ The backend receives the request and attempts to verify the token.
_ **Backend Configuration:** The backend is configured with `CLERK_PUB_JWT_URL` containing three issuers, including the development one:
_ `https://clerk.fireproof.direct`
_ `https://clerk.vibes.diy`
_ `https://sincere-cheetah-30.clerk.accounts.dev`
_ **Response:** HTTP 500 \* **Error Message:**
`json
{
"type": "error",
"message": "No well-known JWKS URL could verify the token:\n[
{
"type": "error",
"error": {
"reason": "token-invalid-signature"
},
"url": "https://clerk.fireproof.direct/.well-known/jwks.json"
},
{
"type": "error",
"error": {
"reason": "token-invalid-signature"
},
"url": "https://clerk.vibes.diy/.well-known/jwks.json"
},
{
"type": "error",
"error": {
"reason": "token-invalid-signature"
},
"url": "https://sincere-cheetah-30.clerk.accounts.dev/.well-known/jwks.json"
}
]"
}
`

## Diagnosis Findings

- **✅ Issuer Match:** The client sends a token from `sincere-cheetah-30`. The backend _is_ attempting to verify against the corresponding JWKS URL.
- **✅ Key ID Match:** The client token has `kid: 'ins_35qNS5Jwyc7z4aJRBIS7o205yzb'`. The public JWKS at `https://sincere-cheetah-30.clerk.accounts.dev/.well-known/jwks.json` currently contains this exact key.
- **❌ Signature Verification Failure:** Despite the correct issuer and key ID, the backend reports `token-invalid-signature`.

## Potential Root Causes for Review

1. **Audience (`aud`) Validation:**
- The client token has **no `aud` claim**.
- Does the backend's `@hono/clerk-auth` middleware (or underlying `verifyToken` function) enforce a default audience check? If so, the lack of an audience would cause verification to fail even if the signature is valid.

2. **Stale JWKS Cache:**
- Could the backend be caching an older version of the JWKS for `sincere-cheetah-30` that does not yet contain the key `ins_35qNS5Jwyc7z4aJRBIS7o205yzb`?
- _Note: This is less likely if this is a stable dev instance, but possible if keys were recently rotated._

3. **Crypto/Environment Mismatch:**
- Is the backend environment (e.g., Cloudflare Workers) correctly handling the RS256 signature verification for this specific key?

## Request for Engineering Team

Please verify if the Fireproof backend enforces an **Audience (`aud`) claim** on Clerk tokens. If so, what audience value is expected? We may need to configure our Clerk development instance to include this audience in its issued tokens.
16 changes: 11 additions & 5 deletions vibes.diy/pkg/app/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,17 @@ class vibesDiyEnv {
this.env().get("VITE_CONNECT_URL") ??
"https://connect.fireproof.direct/token",
);
readonly CONNECT_API_URL = Lazy(
() =>
this.env().get("VITE_CONNECT_API_URL") ??
"https://connect.fireproof.direct/api",
);
readonly CONNECT_API_URL = Lazy(() => {
const envUrl = this.env().get("VITE_CONNECT_API_URL");
if (envUrl) {
return envUrl;
}
const isProduction =
runtimeFn().isBrowser && window.location.hostname === "vibes.diy";
return isProduction
? "https://connect.fireproof.direct/api"
: "https://dev.connect.fireproof.direct/api";
});
readonly CLOUD_SESSION_TOKEN_PUBLIC_KEY = Lazy(
() =>
this.env().get("VITE_CLOUD_SESSION_TOKEN_PUBLIC") ??
Expand Down
49 changes: 31 additions & 18 deletions vibes.diy/pkg/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ import { ClerkProvider } from "@clerk/clerk-react";
import { CookieConsentProvider } from "./contexts/CookieConsentContext.js";
import { ThemeProvider } from "./contexts/ThemeContext.js";
import { getLibraryImportMap } from "./config/import-map.js";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

// Create a client instance for React Query
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
refetchOnWindowFocus: false,
},
},
});
Comment on lines +23 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The global QueryClient instance is created at module scope with default options, which is good, but there is no strategy for cache invalidation on auth changes. Because the QueryClientProvider wraps the entire app, when a user signs out and another user signs in, any user-scoped queries (like your dashboard data) will remain in the cache unless keys or session boundaries are managed explicitly.

Especially when combined with static query keys, this can create subtle cross-user data exposure within the same browser session. Even if you fix the keys, it's generally a good idea to reset or partially clear the query cache on auth state transitions for user-scoped data.

Suggestion

Consider either scoping the QueryClient to the current auth session or listening to Clerk auth events to clear user-specific queries on sign-out:

// example: create a hook or effect near your auth boundary
useEffect(() => {
  if (!isSignedIn && isLoaded) {
    queryClient.clear();
  }
}, [isSignedIn, isLoaded]);

Alternatively, if you segment query keys by userId, target only those keys with removeQueries. This keeps the global cache while preventing leakage of user-specific data across sessions. Reply with "@CharlieHelps yes please" if you'd like me to add a commit wiring this up with Clerk auth events.


export const links: Route.LinksFunction = () => {
const rawBase = VibesDiyEnv.APP_BASENAME();
Expand Down Expand Up @@ -120,24 +131,26 @@ export function Layout({ children }: { children: React.ReactNode }) {
{/* TODO: Re-enable GtmNoScript when consent can be checked server-side */}
{/* <GtmNoScript /> */}
<ClerkProvider publishableKey={VibesDiyEnv.CLERK_PUBLISHABLE_KEY()}>
<ThemeProvider>
<PostHogProvider
apiKey={VibesDiyEnv.POSTHOG_KEY()}
options={{
api_host: VibesDiyEnv.POSTHOG_HOST(),
opt_out_capturing_by_default: true,
}}
>
<CookieConsentProvider>
{children}
<ClientOnly>
<CookieBanner />
</ClientOnly>
</CookieConsentProvider>
<ScrollRestoration data-testid="scroll-restoration" />
<Scripts data-testid="scripts" />
</PostHogProvider>
</ThemeProvider>
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<PostHogProvider
apiKey={VibesDiyEnv.POSTHOG_KEY()}
options={{
api_host: VibesDiyEnv.POSTHOG_HOST(),
opt_out_capturing_by_default: true,
}}
>
<CookieConsentProvider>
{children}
<ClientOnly>
<CookieBanner />
</ClientOnly>
</CookieConsentProvider>
<ScrollRestoration data-testid="scroll-restoration" />
<Scripts data-testid="scripts" />
</PostHogProvider>
</ThemeProvider>
</QueryClientProvider>
</ClerkProvider>
</body>
</html>
Expand Down
1 change: 1 addition & 0 deletions vibes.diy/pkg/app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default [

route("settings", "./routes/settings.tsx", { id: "settings" }),
route("about", "./routes/about.tsx", { id: "about" }),
route("fireproof", "./routes/fireproof.tsx", { id: "fireproof" }),
route("sso-callback", "./routes/sso-callback.tsx", { id: "sso-callback" }),
route("remix/:vibeSlug?", "./routes/remix.tsx", { id: "remix" }),
route("vibe/:titleId/:installId", "./routes/vibe.$titleId.$installId.tsx", {
Expand Down
Loading