| 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:
- Import
requireAuthRedirectfrom@/lib/auth/server-auth - Call
await requireAuthRedirect()at the start of the component - User will be redirected to
/loginif not authenticated
import { requireAuthRedirect } from '@/lib/auth/server-auth';
export default async function ProtectedPage() {
await requireAuthRedirect();
// User guaranteed authenticated here
return <YourContent />;
}
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:
- Import
requireAuthfrom@/lib/auth/server-auth - Call
const user = await requireAuth()at the start of the action - Action will throw
UnauthorizedErrorif user not authenticated
'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 ingetUserData()- 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:
import { requireAdminRedirect } from '@/lib/auth/server-auth';
export default async function AdminPage() {
await requireAdminRedirect();
// User guaranteed to be admin
return <AdminPanel />;
}
To restrict a Server Action to admins:
'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:
import { isAdmin } from '@/lib/auth/server-auth';
export default async function Page() {
const userIsAdmin = await isAdmin();
return (
<>
<RegularContent />
{userIsAdmin && <AdminControls />}
</>
);
}
Enforcing Multi-Tenant Data Access
To ensure users only access their own family's data:
'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):
- Create page under
src/app/(auth)/page-name/ - The
(auth)group layout automatically redirects authenticated users to/dashboard - Create corresponding Server Action in
actions.tsfile - 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
- Enable provider in Supabase Dashboard
- Use
supabase.auth.signInWithOAuth({ provider: 'github', options: {...} }) - 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.tsare server-side only - Never import these in Client Components
- Never add
'use server'directive toserver-auth.ts(breaks class exports)
Middleware
- Middleware automatically refreshes tokens on every request
- Uses
updateSession()fromsrc/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 organizationreferences/security.md- Security best practices and requirementsreferences/patterns.md- Code examples and common patternsreferences/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