Caret delegates identity entirely to Supabase Auth. There is no custom password hashing, no home-grown session store, and no separate user table for authentication —Documentation Index
Fetch the complete documentation index at: https://mintlify.com/arrozet/caret/llms.txt
Use this file to discover all available pages before exploring further.
auth.users inside Supabase is the single source of truth for every account. Once a user signs in, Supabase issues a signed JWT that every backend service and the collab-service validates independently — using Supabase’s JWKS endpoint — before processing any request.
Identity: Supabase Auth
All user accounts live inauth.users, which is managed exclusively by Supabase. The application extends this with a user_profiles table in the public schema that stores display preferences (display name, avatar URL, locale). Because user_profiles is separate from auth.users, re-authenticating through a provider like Google OAuth does not overwrite the user’s customized profile.
Frontend Auth Flow
The frontend uses the Supabase JS client for all auth operations. Auth state is held in a Zustand store (authStore.ts) and exposed to the rest of the application through React hooks and context.
Sign in
The user signs in via the auth modal (email/password or OAuth provider). Supabase JS calls Supabase Auth and receives an access token (JWT) and a refresh token.
Session storage
Supabase JS persists the session to
localStorage. The Zustand authStore subscribes to supabase.auth.onAuthStateChange and updates its state whenever the session changes (sign in, sign out, token refresh).Profile upsert
After successful sign-in,
authStore reads and upserts user_profiles via the Supabase JS anon client using the authenticated session. This is the only direct Supabase JS database call the frontend makes.API requests
For all other data operations, the frontend calls the REST API through
src/lib/apiClient.ts, which reads the current session’s access token and attaches it as Authorization: Bearer <token> on every request.JWT Validation Per Service
Supabase issues asymmetric JWTs (ES256). Every backend service validates incoming tokens independently — there is no centralized auth proxy in the API Gateway. Each service fetches Supabase’s public keys fromSUPABASE_URL/auth/v1/.well-known/jwks.json and caches them locally (5-minute TTL) to avoid fetching on every request.
| Service | Validation method |
|---|---|
api-gateway | No JWT validation — applies CORS and rate limiting only; validation is the responsibility of each downstream service |
auth-service | Validates JWT via JWKS (ES256); used for auth-scoped endpoints |
document-service | Validates JWT via JWKS (ES256); extracts user_id from sub claim for repository queries |
collab-service | Validates JWT on WebSocket handshake via JWKS (ES256/RS256); also accepts HS256 tokens using SUPABASE_JWT_SECRET as a fallback (see below) |
ai-service | Validates JWT via JWKS (ES256/RS256) using a FastAPI Depends() dependency; extracts user_id for conversation scoping |
| Variable | Services that use it | Purpose |
|---|---|---|
SUPABASE_URL | All backend services | Supabase project URL; used to fetch the JWKS endpoint (/auth/v1/.well-known/jwks.json) |
SUPABASE_ANON_KEY | All backend services | Sent as apikey header when fetching the JWKS from Supabase GoTrue |
SUPABASE_SERVICE_ROLE_KEY | auth-service | Admin DB key for operations that must bypass RLS |
SUPABASE_JWT_SECRET | collab-service only | HS256 symmetric fallback; used when the JWT header declares alg: HS256 instead of ES256 |
JWT_SECRET | auth-service, document-service | Additional secret for internal service-level token validation |
Collaboration Auth
The collab-service is not behind the API Gateway, so it performs its own JWT verification. The frontend passes the Supabase access token as a query parameter on the WebSocket URL:- Extracts the
tokenquery parameter from the upgrade request. - Inspects the JWT header to determine the signing algorithm.
- For ES256/RS256 tokens, validates the signature against the Supabase JWKS. For HS256 tokens, validates using
SUPABASE_JWT_SECRET. - Rejects the connection with a
401close frame if the token is missing, malformed, or expired. - Allows the connection and records the authenticated
user_idfor awareness and audit purposes.
JWTs have limited lifetimes. Long-lived collaboration sessions may need to reconnect after token expiry. The frontend
useCollaborationSession hook is responsible for managing reconnection when Supabase JS issues a refreshed token.Protected Routes
On the frontend, authenticated routes are wrapped with theAuthGuard component. If the user has no valid session, AuthGuard redirects them to /login before rendering the protected view.
| Route | Protection |
|---|---|
/documents | AuthGuard required |
/documents/:id | AuthGuard required |
/settings | AuthGuard required |
/ | Public (landing page) |
/login | Public (landing page with auth modal) |
/debug/collab-harness | Development only |
Row-Level Security
Even when a request passes JWT validation at the service layer, PostgreSQL RLS provides a second enforcement boundary at the database level. RLS policies are defined in SQL migration files and evaluate on every query. How backend services interact with RLS:Service-role credentials
Backend services typically connect with the
SUPABASE_SERVICE_ROLE_KEY, which bypasses RLS. This is appropriate for admin operations (creating workspaces, generating version snapshots, writing Y.js updates) that the service itself initiates on behalf of the user.User-scoped JWT
When a service needs to enforce RLS for user-scoped queries (e.g. validating workspace membership before returning data), it can pass the user’s JWT to Supabase and let RLS policies filter the result set automatically.
user_profiles. RLS on user_profiles permits each authenticated user to read and write only their own row. All other application tables are inaccessible to the frontend anon client.
Policy source files:
| Table group | Policy file |
|---|---|
| Core document tables | document-service/src/db/migrations/001_rls_core_tables.sql |
| Collaboration tables | collab-service/src/db/migrations/001_rls_collab_tables.sql |
| AI tables | ai-service/src/db/migrations/versions/0003_enable_rls_on_public_tables.py |