--- name: auth description: Authentication and access control skill for Next.js 15 + Supabase applications. Use when implementing user authentication, protecting routes, managing user sessions, enforcing role-based access control (admin/member), or working with multi-tenant family-based data isolation. Covers login/logout, registration with email verification, OAuth (GitHub), route protection for Server Components and Server Actions, admin-only features, and multi-tenant data access patterns. --- # Authentication & Access Control This skill provides workflows for implementing authentication and access control in this Next.js 15 + Supabase application using server-side auth with httpOnly cookies, hybrid route protection, and multi-tenant family-based data isolation. ## System Overview - **Auth Provider**: Supabase Auth with httpOnly cookies - **Architecture**: Next.js 15 App Router with Server Components and Server Actions - **Route Protection**: Hybrid approach (page-level auth checks, not middleware-only) - **Multi-Tenancy**: Family-based data isolation with RLS policies - **Roles**: Admin (first user in family) and Member ## Core Workflows ### Protecting a New Route To protect a route from unauthenticated users: 1. Import `requireAuthRedirect` from `@/lib/auth/server-auth` 2. Call `await requireAuthRedirect()` at the start of the component 3. User will be redirected to `/login` if not authenticated ```typescript import { requireAuthRedirect } from '@/lib/auth/server-auth'; export default async function ProtectedPage() { await requireAuthRedirect(); // User guaranteed authenticated here return ; } ``` To protect an entire route group, add this to the layout component. All child routes will inherit the protection. ### Protecting a Server Action To require authentication in a Server Action: 1. Import `requireAuth` from `@/lib/auth/server-auth` 2. Call `const user = await requireAuth()` at the start of the action 3. Action will throw `UnauthorizedError` if user not authenticated ```typescript 'use server'; import { requireAuth } from '@/lib/auth/server-auth'; export async function myAction() { const user = await requireAuth(); // Proceed with authenticated action } ``` ### Getting Current User Data - `getCurrentUser()` - Auth user (email, id), returns null if not logged in - `getUserData()` - Extended profile (role, familyId, firstName, lastName, active) - `getCurrentFamilyId()` - Just the family ID All use React `cache()` - multiple calls in same request return cached value. ### Implementing Admin-Only Features To restrict a page to admins: ```typescript import { requireAdminRedirect } from '@/lib/auth/server-auth'; export default async function AdminPage() { await requireAdminRedirect(); // User guaranteed to be admin return ; } ``` To restrict a Server Action to admins: ```typescript 'use server'; import { requireAdmin } from '@/lib/auth/server-auth'; export async function adminAction() { await requireAdmin(); // Throws if not admin // Proceed } ``` To conditionally show admin UI: ```typescript import { isAdmin } from '@/lib/auth/server-auth'; export default async function Page() { const userIsAdmin = await isAdmin(); return ( <> {userIsAdmin && } ); } ``` ### Enforcing Multi-Tenant Data Access To ensure users only access their own family's data: ```typescript 'use server'; import { requireFamilyAccess } from '@/lib/auth/server-auth'; export async function updateFamilyData(familyId: string, data: any) { await requireFamilyAccess(familyId); // Throws if user not in this family // User guaranteed to belong to this family await updateDatabase(familyId, data); } ``` When fetching current user's family data, use `getCurrentFamilyId()` instead - no need for `requireFamilyAccess` since it's their own family. ### Adding a New Authentication Page To create a new auth page (login, register, password reset): 1. Create page under `src/app/(auth)/page-name/` 2. The `(auth)` group layout automatically redirects authenticated users to `/dashboard` 3. Create corresponding Server Action in `actions.ts` file 4. Import and use Supabase client: `const supabase = await createClient()` Pages in `(auth)` group are automatically protected from authenticated users - they'll be redirected to dashboard if already logged in. ### Implementing Login/Logout Use `supabase.auth.signInWithPassword()` for login and `supabase.auth.signOut()` for logout. See `references/patterns.md` for complete code examples. ### Adding OAuth Providers 1. Enable provider in Supabase Dashboard 2. Use `supabase.auth.signInWithOAuth({ provider: 'github', options: {...} })` 3. Callback handled automatically by `src/app/auth/callback/route.ts` See `references/patterns.md` for full implementation examples. ## Security Requirements ### Token Validation - Always use `supabase.auth.getUser()` to validate tokens (revalidates with server) - Never use `supabase.auth.getSession()` in server code (can be spoofed) ### Server-Side Only - All auth helpers in `src/lib/auth/server-auth.ts` are server-side only - Never import these in Client Components - Never add `'use server'` directive to `server-auth.ts` (breaks class exports) ### Middleware - Middleware automatically refreshes tokens on every request - Uses `updateSession()` from `src/lib/supabase/middleware.ts` - Calls `getUser()` to revalidate tokens - No additional token refresh logic needed ## Reference Files For detailed information, see: - `references/file-tree.md` - Complete file structure and organization - `references/security.md` - Security best practices and requirements - `references/patterns.md` - Code examples and common patterns - `references/flows.md` - Authentication flow diagrams ## Quick Reference **Key Files**: `src/lib/auth/server-auth.ts` (helpers), `src/middleware.ts` (token refresh), `src/app/dashboard/layout.tsx` (dashboard protection) **Environment**: `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`, `NEXT_PUBLIC_SITE_URL` **Database**: `auth.users` (Supabase auth), `public.families` (households), `public.users` (profiles) ## Common Issues **'use server' export error**: Remove `'use server'` from `server-auth.ts` - these are utilities, not actions **Middleware redirect fails**: Use `requireAuth()` in Server Actions - middleware redirects don't work with POST **Multi-tenant access denied**: Use `requireFamilyAccess(familyId)` to validate family ownership