skills/authentication-system/SKILL.md
Dual JWT authentication system (visitor + user tokens) with Supabase. Use when implementing auth flows, token management, client-side token selection/caching, or working with auth state in React components.
npx skillsauth add bkinsey808/songshare-effect authentication-systemInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Requires: file-read, terminal (linting/testing). No network access needed.
Use this skill when:
react/src/lib/supabase/.Execution workflow:
npm run lint.Output requirements:
Covers SongShare's dual authentication architecture:
react/src/lib/supabase/token/token-cache.tsgetSupabaseAuthToken resolves the right token without hooksRLS policies and Realtime subscription patterns are in realtime-rls-architecture skill.
Visitor Token (anonymous):
app_metadata: { visitor_id: "uuid" }*_public tablesUser Token (authenticated):
app_metadata: { visitor_id: "uuid", user: { user_id: "app-uuid" } }Both tokens are issued against a single shared "visitor" Supabase auth account so that Realtime's WebSocket requirement (must have a valid JWT) is always satisfied.
| Purpose | File |
| -------------------------------- | -------------------------------------------------------------------- |
| Select token for Supabase client | react/src/lib/supabase/auth-token/getSupabaseAuthToken.ts |
| Fetch visitor token from API | react/src/lib/supabase/auth-token/getSupabaseClientToken.ts |
| Fetch user token from API | react/src/lib/supabase/auth-token/fetchSupabaseUserTokenFromApi.ts |
| In-memory token cache | react/src/lib/supabase/token/token-cache.ts |
| Authenticated Supabase client | react/src/lib/supabase/client/getSupabaseClientWithAuth.ts |
| Auth slice (Zustand) | react/src/auth/slice/createAuthSlice.ts |
| Auth state types | react/src/auth/slice/auth-slice.types.ts |
| Server: visitor token | api/src/supabase/getSupabaseClientToken.ts |
| Server: user token | api/src/user-session/getUserToken.ts |
getSupabaseAuthToken)getSupabaseAuthToken is a plain async function — not a hook. It never calls useAppStore directly. It works in three steps:
// react/src/lib/supabase/auth-token/getSupabaseAuthToken.ts (simplified)
export default async function getSupabaseAuthToken(): Promise<string | undefined> {
// 1. Return cached user token if still valid
const userToken = getCachedUserToken();
if (userToken !== undefined) return userToken;
// 2. Try to fetch fresh user token from API (deduplicated in-flight)
const fetchedUserToken = await fetchSupabaseUserTokenFromApi();
if (fetchedUserToken !== undefined) return fetchedUserToken;
// 3. Fall back to visitor (client) token
return getSupabaseClientToken();
}
Always use the helper — never construct a bare client with the anon key:
// ✅ GOOD — token is set on the client
const client = await getSupabaseClientWithAuth();
const { data } = await client.from("song_library").select();
// ❌ BAD — no auth token, RLS will block everything
const client = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
Tokens are cached in memory (never localStorage) in token-cache.ts. Export functions:
getCachedUserToken() — returns user token if present and not near-expiry (5 min buffer)cacheUserToken(token, expiryMs) — store user token with expiryimport useAppStore from "@/react/app/useAppStore";
function MyComponent(): ReactElement {
const isSignedIn = useAppStore((state) => state.isSignedIn);
if (isSignedIn) return <p>Welcome back!</p>;
return <p>Sign in to access your library</p>;
}
signIn is called from the auth slice with the decoded UserSessionData:
const signIn = useAppStore((state) => state.signIn);
// signIn receives UserSessionData from the server session response
signIn(userSessionData);
// Internally stores isSignedIn = true and triggers user token fetch
const setIsSignedIn = useAppStore((state) => state.setIsSignedIn);
setIsSignedIn(false); // Clears state; next Supabase call falls back to visitor token
api/src/supabase/getSupabaseClientToken.ts)Signs in as the shared visitor account and returns the JWT. If visitor_id is missing from metadata, sets it and re-signs.
api/src/user-session/getUserToken.ts)Signs in as the visitor account, updates app_metadata with { user: { user_id } }, and re-signs to embed the user context into the JWT.
localStorage.setItem("token", token); // XSS vulnerability
Use in-memory cache only — the token cache does this correctly.
// WRONG — useAppStore cannot be called outside a React component/hook
export async function getToken() {
const isSignedIn = useAppStore((state) => state.isSignedIn); // ❌
}
Check the token cache or fetch from the API directly instead.
docs/ai/rules.md.supabase-client-patterns.realtime-rls-architecture.tools
Zustand state management patterns for this project — store creation, selectors, Immer middleware, async actions with loading states, devtools, persist, and testing. Use when authoring or editing Zustand stores (use*Store files) or components that subscribe to stores. Do NOT use for React component structure or TypeScript-only utilities.
testing
How to write, update, or split skill files in this repo. Use when creating a new SKILL.md, updating an existing one, or deciding whether to put content in a skill vs. docs/.
development
Complete guide for testing React hooks — renderHook, Documentation by Harness, installStore, fixtures, subscription patterns, lint/compiler traps, and pre-completion checklist. Read docs/testing/unit-test-hook-best-practices.md for the full reference.
development
Vitest unit test authoring for this repo — setup, mocking, API handler testing, and common pitfalls for non-hook code. Use when the user asks to add, update, fix, or review unit tests for utilities, components, API handlers, or scripts. Do NOT use for React hook tests — load unit-test-hook-best-practices instead.