| name | frontend-supabase-auth |
| description | Use when implementing authentication, route guards, or user session management |
Frontend: Supabase Authentication
Authentication is handled entirely through TanStack Router's beforeLoad guards with Supabase session management. Never implement auth checks in components or create custom auth stores.
Core Principles
- Route-level authentication only - Use TanStack Router's async
beforeLoad
- Supabase manages sessions - Never create custom auth stores
- Fresh session checks - Always use
supabase.auth.getSession() (async)
- Preserve redirect paths - Maintain user's intended destination
- Security-critical queries - Direct database checks for whitelist/membership
Authentication Pattern
Always check authentication in route files using beforeLoad:
export const Route = createFileRoute("/_protected")({
beforeLoad: async ({ location }) => {
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
const redirectPath = `${location.pathname}${location.search ? `?${new URLSearchParams(location.search).toString()}` : ""}`;
throw redirect({
to: "/login",
search: { redirect: redirectPath },
});
}
},
});
Sign Out Pattern
Use Supabase's signOut() method with proper error handling:
const handleSignOut = async () => {
try {
const { error } = await supabase.auth.signOut();
if (error) throw error;
message.success("Signed out successfully");
navigate({ to: "/login" });
} catch (error) {
console.error("Error signing out:", error);
message.error("Failed to sign out. Please try again.");
}
};
Key Rules
- beforeLoad is the ONLY security boundary - Components assume access is granted
- No cached session data - Always query fresh for security-critical checks
- Direct database queries - Use
supabase.from() for whitelist/membership verification
- Redirect on failure - Never render protected content on auth failure
- Async pattern -
getSession() must be awaited
Common Patterns
Whitelist Check:
const { data: profile } = await supabase
.from("profiles")
.select("whitelisted")
.eq("id", session.user.id)
.single();
if (!profile?.whitelisted) {
throw redirect({ to: "/not-whitelisted" });
}
Membership Guard:
const { data: membership } = await supabase
.from("organization_members")
.select("*")
.eq("user_id", session.user.id)
.eq("organization_id", organizationId)
.maybeSingle();
if (!membership) {
throw redirect({ to: "/access-denied" });
}
Reference
For implementation examples: .claude/skills/frontend-supabase-auth/examples.md
For anti-patterns: .claude/skills/frontend-supabase-auth/reference.md