From 54ccd43ce11cf4cb730d33086efbe3db68b285be Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 18:46:08 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 12 + README.md | 3 + plugin.lock.json | 49 ++ skills/auth-route-protection-checker/SKILL.md | 498 ++++++++++++ .../references/protection-patterns.md | 719 ++++++++++++++++++ 5 files changed, 1281 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 plugin.lock.json create mode 100644 skills/auth-route-protection-checker/SKILL.md create mode 100644 skills/auth-route-protection-checker/references/protection-patterns.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..96e29d9 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "auth-route-protection-checker", + "description": "This skill should be used when the user requests to audit, check, or generate authentication and authorization protection for Next.js routes, server components, API routes, and server actions. It analyzes existing routes for missing auth checks and generates protection logic based on user roles and permissions. Trigger terms include auth check, route protection, protect routes, secure endpoints, auth middleware, role-based routes, authorization check, api security, server action security, protec", + "version": "1.0.0", + "author": { + "name": "Hope Overture", + "email": "support@worldbuilding-app-skills.dev" + }, + "skills": [ + "./skills" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..967046a --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# auth-route-protection-checker + +This skill should be used when the user requests to audit, check, or generate authentication and authorization protection for Next.js routes, server components, API routes, and server actions. It analyzes existing routes for missing auth checks and generates protection logic based on user roles and permissions. Trigger terms include auth check, route protection, protect routes, secure endpoints, auth middleware, role-based routes, authorization check, api security, server action security, protec diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..38cbca3 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,49 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:hopeoverture/worldbuilding-app-skills:plugins/auth-route-protection-checker", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "5335df851dfb89ddd57b0f29074507641e78d354", + "treeHash": "e7a4c956cd201a8009dac593052586795b799b1b686912f31edb9f35da7f6574", + "generatedAt": "2025-11-28T10:17:30.611966Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "auth-route-protection-checker", + "description": "This skill should be used when the user requests to audit, check, or generate authentication and authorization protection for Next.js routes, server components, API routes, and server actions. It analyzes existing routes for missing auth checks and generates protection logic based on user roles and permissions. Trigger terms include auth check, route protection, protect routes, secure endpoints, auth middleware, role-based routes, authorization check, api security, server action security, protec", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "fa7ae1006f69536edb7729f2b7f443562a556f524715e5909120943e7efed949" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "50aeab57ac145de2f3c7b625e83513c0acde02b63e1c5fb217e26073c0398dec" + }, + { + "path": "skills/auth-route-protection-checker/SKILL.md", + "sha256": "8bcac435e8b8e1bcab9d8faf5d11722d7e6d5c33bac8f5d1c8384d6b0620467b" + }, + { + "path": "skills/auth-route-protection-checker/references/protection-patterns.md", + "sha256": "a9f241e4cdda9aee5e0bad8159762566eb4ab1b2c61559a43edd428f7ab86bbb" + } + ], + "dirSha256": "e7a4c956cd201a8009dac593052586795b799b1b686912f31edb9f35da7f6574" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/auth-route-protection-checker/SKILL.md b/skills/auth-route-protection-checker/SKILL.md new file mode 100644 index 0000000..7420dc2 --- /dev/null +++ b/skills/auth-route-protection-checker/SKILL.md @@ -0,0 +1,498 @@ +--- +name: auth-route-protection-checker +description: This skill should be used when the user requests to audit, check, or generate authentication and authorization protection for Next.js routes, server components, API routes, and server actions. It analyzes existing routes for missing auth checks and generates protection logic based on user roles and permissions. Trigger terms include auth check, route protection, protect routes, secure endpoints, auth middleware, role-based routes, authorization check, api security, server action security, protect pages. +allowed-tools: Read, Grep, Glob, Bash +--- + +# Auth Route Protection Checker + +To audit and enhance authentication protection across Next.js routes, server components, and API routes, follow these steps systematically. + +## Step 1: Discover Project Structure + +Identify all files that need authentication checks: + +1. Use Glob to find all route files: + - `app/**/page.tsx` - Page components + - `app/**/route.ts` - API routes + - `app/**/layout.tsx` - Layout components + - `lib/actions/**/*.ts` - Server actions + +2. Read middleware configuration: + - `middleware.ts` - Current middleware setup + - `next.config.js` - Route configuration + +3. Identify authentication setup: + - Search for auth client files (Supabase, NextAuth, Clerk, etc.) + - Find auth utility functions + +## Step 2: Analyze Current Protection + +For each discovered file, check for existing auth protection: + +### Check for Authentication Patterns + +Use Grep to search for: +``` +- "auth.getUser()" +- "getSession()" +- "currentUser()" +- "requireAuth" +- "redirect.*login" +- "unauthorized" +- "createServerClient" +``` + +### Identify Protection Gaps + +Flag files that: +- Have no auth checks +- Are in protected routes but lack verification +- Accept user input without auth validation +- Perform privileged operations without role checks + +Consult `references/protection-patterns.md` for common patterns. + +## Step 3: Categorize Routes by Protection Level + +Classify routes into security categories: + +**Public Routes** - No auth required: +- Landing pages +- Marketing content +- Public blog posts +- Login/signup pages + +**Authenticated Routes** - Login required: +- User dashboard +- Profile pages +- User-specific data + +**Role-Protected Routes** - Specific roles required: +- Admin panels +- Moderator tools +- Premium features + +**Action-Protected Routes** - Specific permissions required: +- Edit operations +- Delete operations +- Admin actions + +## Step 4: Generate Protection Report + +Create a comprehensive audit report: + +```markdown +# Route Protection Audit Report + +Generated: [timestamp] + +## Summary +- Total Routes: X +- Protected: Y +- Unprotected: Z +- Needs Review: N + +## Unprotected Routes + +### Critical (Requires immediate attention) +- [ ] /app/admin/page.tsx - Admin panel with no auth check +- [ ] /app/api/users/delete/route.ts - Delete endpoint unprotected + +### High Priority +- [ ] /app/dashboard/page.tsx - User dashboard missing auth +- [ ] /app/api/data/route.ts - API route needs auth + +### Medium Priority +- [ ] /app/profile/page.tsx - Profile page needs verification + +### Low Priority (Review recommended) +- [ ] /app/about/page.tsx - Consider if auth needed + +## Protected Routes + +### Properly Protected +- [x] /app/(protected)/settings/page.tsx - Has auth check +- [x] /app/api/auth/logout/route.ts - Auth verified + +### Needs Enhancement +- [~] /app/admin/users/page.tsx - Has auth but no role check +- [~] /app/api/posts/route.ts - Auth exists but no rate limiting +``` + +## Step 5: Generate Protection Code + +For each unprotected route, generate appropriate protection code: + +### Server Component Protection + +```typescript +// app/protected-page/page.tsx +import { createServerClient } from '@/lib/supabase/server' +import { redirect } from 'next/navigation' + +export default async function ProtectedPage() { + const supabase = createServerClient() + + const { data: { user }, error } = await supabase.auth.getUser() + + if (error || !user) { + redirect('/login') + } + + // Optional: Role check + const { data: profile } = await supabase + .from('profiles') + .select('role') + .eq('id', user.id) + .single() + + if (profile?.role !== 'admin') { + redirect('/unauthorized') + } + + return
Protected Content
+} +``` + +### API Route Protection + +```typescript +// app/api/protected/route.ts +import { createServerClient } from '@/lib/supabase/server' +import { NextResponse } from 'next/server' + +export async function GET(request: Request) { + const supabase = createServerClient() + + const { data: { user }, error } = await supabase.auth.getUser() + + if (error || !user) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ) + } + + // Optional: Role-based access + const userRole = user.user_metadata?.role + + if (userRole !== 'admin') { + return NextResponse.json( + { error: 'Forbidden - Admin access required' }, + { status: 403 } + ) + } + + // Protected logic here + return NextResponse.json({ data: 'protected data' }) +} +``` + +### Server Action Protection + +```typescript +// lib/actions/admin.ts +'use server' + +import { createServerClient } from '@/lib/supabase/server' +import { revalidatePath } from 'next/cache' + +export async function deleteUser(userId: string) { + const supabase = createServerClient() + + // Auth check + const { data: { user }, error } = await supabase.auth.getUser() + + if (error || !user) { + throw new Error('Unauthorized') + } + + // Role check + const { data: profile } = await supabase + .from('profiles') + .select('role') + .eq('id', user.id) + .single() + + if (profile?.role !== 'admin') { + throw new Error('Forbidden - Admin access required') + } + + // Permission check (optional) + const canDeleteUsers = await checkPermission(user.id, 'users:delete') + if (!canDeleteUsers) { + throw new Error('Insufficient permissions') + } + + // Perform action + const { error: deleteError } = await supabase + .from('users') + .delete() + .eq('id', userId) + + if (deleteError) throw deleteError + + revalidatePath('/admin/users') +} +``` + +### Middleware Protection + +```typescript +// middleware.ts +import { createServerClient } from '@/lib/supabase/middleware' +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +export async function middleware(request: NextRequest) { + const response = NextResponse.next() + const supabase = createServerClient(request, response) + + const { data: { user } } = await supabase.auth.getUser() + + // Protected routes + const protectedRoutes = ['/dashboard', '/profile', '/settings'] + const isProtectedRoute = protectedRoutes.some(route => + request.nextUrl.pathname.startsWith(route) + ) + + if (isProtectedRoute && !user) { + return NextResponse.redirect(new URL('/login', request.url)) + } + + // Admin routes + const adminRoutes = ['/admin'] + const isAdminRoute = adminRoutes.some(route => + request.nextUrl.pathname.startsWith(route) + ) + + if (isAdminRoute) { + if (!user) { + return NextResponse.redirect(new URL('/login', request.url)) + } + + const { data: profile } = await supabase + .from('profiles') + .select('role') + .eq('id', user.id) + .single() + + if (profile?.role !== 'admin') { + return NextResponse.redirect(new URL('/unauthorized', request.url)) + } + } + + return response +} + +export const config = { + matcher: [ + '/dashboard/:path*', + '/profile/:path*', + '/settings/:path*', + '/admin/:path*', + ] +} +``` + +## Step 6: Generate Helper Functions + +Create reusable auth utilities using templates from `assets/auth-helpers.ts`: + +```typescript +// lib/auth/helpers.ts +import { createServerClient } from '@/lib/supabase/server' +import { redirect } from 'next/navigation' + +export async function requireAuth() { + const supabase = createServerClient() + const { data: { user }, error } = await supabase.auth.getUser() + + if (error || !user) { + redirect('/login') + } + + return user +} + +export async function requireRole(allowedRoles: string[]) { + const user = await requireAuth() + + const supabase = createServerClient() + const { data: profile } = await supabase + .from('profiles') + .select('role') + .eq('id', user.id) + .single() + + if (!profile || !allowedRoles.includes(profile.role)) { + redirect('/unauthorized') + } + + return { user, role: profile.role } +} + +export async function checkPermission( + userId: string, + permission: string +): Promise { + const supabase = createServerClient() + + const { data } = await supabase + .from('user_permissions') + .select('permission') + .eq('user_id', userId) + .eq('permission', permission) + .single() + + return !!data +} +``` + +## Step 7: Create Testing Suite + +Generate tests to verify protection works: + +Use templates from `assets/auth-tests.ts`: + +```typescript +// tests/auth-protection.test.ts +import { describe, it, expect } from 'vitest' +import { GET } from '@/app/api/protected/route' + +describe('Route Protection', () => { + it('returns 401 for unauthenticated requests', async () => { + const request = new Request('http://localhost/api/protected') + const response = await GET(request) + + expect(response.status).toBe(401) + }) + + it('returns 403 for unauthorized role', async () => { + // Mock auth with non-admin user + const response = await GET(mockRequestWithUser({ role: 'user' })) + + expect(response.status).toBe(403) + }) + + it('allows access for admin users', async () => { + const response = await GET(mockRequestWithUser({ role: 'admin' })) + + expect(response.status).toBe(200) + }) +}) +``` + +## Step 8: Generate Documentation + +Create documentation for the protection system: + +```markdown +# Authentication & Authorization Guide + +## Overview +This application uses [Auth Provider] for authentication and role-based access control. + +## Route Protection Levels + +### Public Routes +- No authentication required +- Accessible to all visitors +- Examples: /, /about, /login + +### Authenticated Routes +- Requires user login +- No specific role needed +- Examples: /dashboard, /profile + +### Role-Protected Routes +- Requires specific role(s) +- Examples: /admin (admin role) + +### Permission-Protected Routes +- Requires specific permissions +- Granular access control +- Examples: /admin/delete-user (users:delete permission) + +## Implementation Patterns + +[Include code examples and usage guidelines] +``` + +## Step 9: Suggest Improvements + +Based on the audit, suggest security enhancements: + +1. **Middleware Coverage**: Routes missing from middleware config +2. **Consistent Patterns**: Inconsistent auth check implementations +3. **Error Handling**: Better error messages and redirects +4. **Rate Limiting**: API routes needing rate limits +5. **CSRF Protection**: Forms needing CSRF tokens +6. **Audit Logging**: Privileged actions needing logging + +Consult `references/security-best-practices.md` for recommendations. + +## Implementation Guidelines + +### Best Practices +- Always check auth on server side, never trust client +- Use middleware for route-based protection +- Add role/permission checks for sensitive operations +- Implement proper error handling and redirects +- Log authentication failures and suspicious activity +- Use HTTPS in production +- Implement rate limiting on auth endpoints + +### Common Patterns + +Consult `references/protection-patterns.md` for: +- Server component auth checks +- API route protection +- Server action security +- Middleware configuration +- Role-based access control +- Permission-based access control + +## Output Format + +Generate files: +``` +reports/ + auth-audit-[timestamp].md +security/ + auth-helpers.ts (if missing) + middleware.ts (enhanced version) +tests/ + auth-protection.test.ts +docs/ + auth-guide.md +``` + +## Verification Checklist + +Before completing: +- [ ] All routes categorized by protection level +- [ ] Critical unprotected routes identified +- [ ] Protection code generated for gaps +- [ ] Helper functions created/updated +- [ ] Middleware configured correctly +- [ ] Tests cover auth scenarios +- [ ] Documentation updated + +## Consulting References + +Throughout analysis: +- Consult `references/protection-patterns.md` for auth patterns +- Consult `references/security-best-practices.md` for guidelines +- Use templates from `assets/auth-helpers.ts` +- Use test templates from `assets/auth-tests.ts` + +## Completion + +When finished: +1. Display audit report summary +2. Highlight critical issues +3. Provide generated protection code +4. List implementation steps +5. Offer to apply fixes or provide guidance diff --git a/skills/auth-route-protection-checker/references/protection-patterns.md b/skills/auth-route-protection-checker/references/protection-patterns.md new file mode 100644 index 0000000..ea830df --- /dev/null +++ b/skills/auth-route-protection-checker/references/protection-patterns.md @@ -0,0 +1,719 @@ +# Authentication Protection Patterns for Next.js + +This reference document provides common authentication and authorization patterns for Next.js App Router applications. + +## Pattern 1: Server Component Auth Check + +Basic authentication check in Server Components. + +```typescript +// app/dashboard/page.tsx +import { createServerClient } from '@/lib/supabase/server' +import { redirect } from 'next/navigation' + +export default async function DashboardPage() { + const supabase = createServerClient() + const { data: { user }, error } = await supabase.auth.getUser() + + if (error || !user) { + redirect('/login') + } + + return ( +
+

Welcome, {user.email}

+ {/* Protected content */} +
+ ) +} +``` + +## Pattern 2: API Route Auth Check + +Protecting API routes with authentication. + +```typescript +// app/api/data/route.ts +import { createServerClient } from '@/lib/supabase/server' +import { NextResponse } from 'next/server' + +export async function GET(request: Request) { + const supabase = createServerClient() + const { data: { user }, error } = await supabase.auth.getUser() + + if (error || !user) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ) + } + + // Fetch user-specific data + const { data } = await supabase + .from('items') + .select('*') + .eq('user_id', user.id) + + return NextResponse.json({ data }) +} + +export async function POST(request: Request) { + const supabase = createServerClient() + const { data: { user }, error } = await supabase.auth.getUser() + + if (error || !user) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ) + } + + const body = await request.json() + + // Insert with user_id + const { data, error: insertError } = await supabase + .from('items') + .insert([{ ...body, user_id: user.id }]) + .select() + + if (insertError) { + return NextResponse.json( + { error: insertError.message }, + { status: 400 } + ) + } + + return NextResponse.json({ data }, { status: 201 }) +} +``` + +## Pattern 3: Server Action Protection + +Securing server actions with auth and validation. + +```typescript +// lib/actions/posts.ts +'use server' + +import { createServerClient } from '@/lib/supabase/server' +import { revalidatePath } from 'next/cache' +import { z } from 'zod' + +const postSchema = z.object({ + title: z.string().min(1).max(200), + content: z.string().min(1), +}) + +export async function createPost(formData: FormData) { + // 1. Auth check + const supabase = createServerClient() + const { data: { user }, error } = await supabase.auth.getUser() + + if (error || !user) { + return { error: 'Unauthorized' } + } + + // 2. Input validation + const rawData = { + title: formData.get('title'), + content: formData.get('content'), + } + + const validatedData = postSchema.safeParse(rawData) + + if (!validatedData.success) { + return { error: 'Invalid input', details: validatedData.error } + } + + // 3. Perform action + const { data, error: insertError } = await supabase + .from('posts') + .insert([{ + ...validatedData.data, + user_id: user.id, + }]) + .select() + .single() + + if (insertError) { + return { error: insertError.message } + } + + // 4. Revalidate + revalidatePath('/posts') + + return { data } +} + +export async function updatePost(postId: string, formData: FormData) { + const supabase = createServerClient() + const { data: { user }, error } = await supabase.auth.getUser() + + if (error || !user) { + return { error: 'Unauthorized' } + } + + // Verify ownership + const { data: post } = await supabase + .from('posts') + .select('user_id') + .eq('id', postId) + .single() + + if (!post || post.user_id !== user.id) { + return { error: 'Forbidden' } + } + + // Validate and update + const rawData = { + title: formData.get('title'), + content: formData.get('content'), + } + + const validatedData = postSchema.safeParse(rawData) + + if (!validatedData.success) { + return { error: 'Invalid input' } + } + + const { data, error: updateError } = await supabase + .from('posts') + .update(validatedData.data) + .eq('id', postId) + .select() + .single() + + if (updateError) { + return { error: updateError.message } + } + + revalidatePath('/posts') + revalidatePath(`/posts/${postId}`) + + return { data } +} +``` + +## Pattern 4: Role-Based Access Control (RBAC) + +Check user roles for access control. + +```typescript +// lib/auth/roles.ts +export type Role = 'user' | 'moderator' | 'admin' + +export async function getUserRole(userId: string): Promise { + const supabase = createServerClient() + + const { data } = await supabase + .from('profiles') + .select('role') + .eq('id', userId) + .single() + + return data?.role || null +} + +export function canAccessRoute(userRole: Role, requiredRoles: Role[]): boolean { + return requiredRoles.includes(userRole) +} + +// Usage in server component +export default async function AdminPage() { + const supabase = createServerClient() + const { data: { user } } = await supabase.auth.getUser() + + if (!user) redirect('/login') + + const role = await getUserRole(user.id) + + if (!canAccessRoute(role, ['admin'])) { + redirect('/unauthorized') + } + + return
Admin Content
+} +``` + +## Pattern 5: Permission-Based Access Control + +Fine-grained permission checking. + +```typescript +// lib/auth/permissions.ts +export type Permission = + | 'posts:read' + | 'posts:create' + | 'posts:update' + | 'posts:delete' + | 'users:read' + | 'users:update' + | 'users:delete' + +export async function hasPermission( + userId: string, + permission: Permission +): Promise { + const supabase = createServerClient() + + // Check user's role-based permissions + const { data: role } = await supabase + .from('profiles') + .select('role') + .eq('id', userId) + .single() + + // Admin has all permissions + if (role?.role === 'admin') return true + + // Check specific permission + const { data } = await supabase + .from('user_permissions') + .select('permission') + .eq('user_id', userId) + .eq('permission', permission) + .single() + + return !!data +} + +// Usage in server action +export async function deleteUser(targetUserId: string) { + 'use server' + + const supabase = createServerClient() + const { data: { user } } = await supabase.auth.getUser() + + if (!user) { + return { error: 'Unauthorized' } + } + + const canDelete = await hasPermission(user.id, 'users:delete') + + if (!canDelete) { + return { error: 'Insufficient permissions' } + } + + // Perform deletion + const { error } = await supabase + .from('users') + .delete() + .eq('id', targetUserId) + + if (error) return { error: error.message } + + revalidatePath('/admin/users') + return { success: true } +} +``` + +## Pattern 6: Middleware Route Protection + +Protect multiple routes with middleware. + +```typescript +// middleware.ts +import { createServerClient } from '@/lib/supabase/middleware' +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +const publicRoutes = ['/', '/about', '/login', '/signup'] +const protectedRoutes = ['/dashboard', '/profile', '/settings'] +const adminRoutes = ['/admin'] + +export async function middleware(request: NextRequest) { + const response = NextResponse.next() + const supabase = createServerClient(request, response) + + const { data: { user } } = await supabase.auth.getUser() + const pathname = request.nextUrl.pathname + + // Allow public routes + if (publicRoutes.includes(pathname)) { + return response + } + + // Check if route requires auth + const requiresAuth = protectedRoutes.some(route => + pathname.startsWith(route) + ) + + if (requiresAuth && !user) { + const redirectUrl = new URL('/login', request.url) + redirectUrl.searchParams.set('redirect', pathname) + return NextResponse.redirect(redirectUrl) + } + + // Check admin routes + const requiresAdmin = adminRoutes.some(route => + pathname.startsWith(route) + ) + + if (requiresAdmin) { + if (!user) { + return NextResponse.redirect(new URL('/login', request.url)) + } + + const { data: profile } = await supabase + .from('profiles') + .select('role') + .eq('id', user.id) + .single() + + if (profile?.role !== 'admin') { + return NextResponse.redirect(new URL('/unauthorized', request.url)) + } + } + + return response +} + +export const config = { + matcher: [ + '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', + ], +} +``` + +## Pattern 7: Layout-Based Protection + +Protect all routes within a layout. + +```typescript +// app/(protected)/layout.tsx +import { createServerClient } from '@/lib/supabase/server' +import { redirect } from 'next/navigation' + +export default async function ProtectedLayout({ + children, +}: { + children: React.ReactNode +}) { + const supabase = createServerClient() + const { data: { user } } = await supabase.auth.getUser() + + if (!user) { + redirect('/login') + } + + return <>{children} +} + +// All pages under app/(protected)/ are now protected +``` + +## Pattern 8: Ownership Verification + +Verify user owns a resource before allowing access. + +```typescript +// app/posts/[id]/edit/page.tsx +import { createServerClient } from '@/lib/supabase/server' +import { redirect, notFound } from 'next/navigation' + +export default async function EditPostPage({ + params, +}: { + params: { id: string } +}) { + const supabase = createServerClient() + const { data: { user } } = await supabase.auth.getUser() + + if (!user) redirect('/login') + + // Fetch post and verify ownership + const { data: post, error } = await supabase + .from('posts') + .select('*') + .eq('id', params.id) + .single() + + if (error || !post) notFound() + + if (post.user_id !== user.id) { + redirect('/unauthorized') + } + + return +} +``` + +## Pattern 9: Multi-Tenant Isolation + +Ensure users can only access their tenant's data. + +```typescript +// lib/auth/tenant.ts +export async function requireTenantAccess(tenantId: string) { + const supabase = createServerClient() + const { data: { user } } = await supabase.auth.getUser() + + if (!user) { + redirect('/login') + } + + // Check tenant membership + const { data: membership } = await supabase + .from('tenant_members') + .select('role') + .eq('tenant_id', tenantId) + .eq('user_id', user.id) + .single() + + if (!membership) { + redirect('/unauthorized') + } + + return { user, role: membership.role } +} + +// Usage +export default async function TenantPage({ + params, +}: { + params: { tenantId: string } +}) { + const { user, role } = await requireTenantAccess(params.tenantId) + + // User has access to this tenant + return
Tenant Content
+} +``` + +## Pattern 10: Helper Functions + +Reusable auth utilities. + +```typescript +// lib/auth/helpers.ts +import { createServerClient } from '@/lib/supabase/server' +import { redirect } from 'next/navigation' +import { cache } from 'react' + +// Cache the current user for the request +export const getCurrentUser = cache(async () => { + const supabase = createServerClient() + const { data: { user } } = await supabase.auth.getUser() + return user +}) + +// Require authentication or redirect +export async function requireAuth() { + const user = await getCurrentUser() + + if (!user) { + redirect('/login') + } + + return user +} + +// Require specific role +export async function requireRole(allowedRoles: string[]) { + const user = await requireAuth() + const supabase = createServerClient() + + const { data: profile } = await supabase + .from('profiles') + .select('role') + .eq('id', user.id) + .single() + + if (!profile || !allowedRoles.includes(profile.role)) { + redirect('/unauthorized') + } + + return { user, role: profile.role } +} + +// Get user with profile +export async function getUserWithProfile() { + const user = await requireAuth() + const supabase = createServerClient() + + const { data: profile } = await supabase + .from('profiles') + .select('*') + .eq('id', user.id) + .single() + + return { user, profile } +} + +// Check if user can perform action +export async function canPerformAction( + action: string, + resourceId?: string +): Promise { + const user = await getCurrentUser() + + if (!user) return false + + // Check permissions logic + const supabase = createServerClient() + + const { data } = await supabase + .rpc('check_user_permission', { + p_user_id: user.id, + p_action: action, + p_resource_id: resourceId, + }) + + return !!data +} +``` + +## Pattern 11: Error Handling + +Proper error handling for auth failures. + +```typescript +// app/api/protected/route.ts +import { createServerClient } from '@/lib/supabase/server' +import { NextResponse } from 'next/server' + +export async function GET(request: Request) { + try { + const supabase = createServerClient() + const { data: { user }, error } = await supabase.auth.getUser() + + if (error) { + console.error('Auth error:', error) + return NextResponse.json( + { error: 'Authentication failed' }, + { status: 401 } + ) + } + + if (!user) { + return NextResponse.json( + { error: 'Unauthorized - Please log in' }, + { status: 401 } + ) + } + + // Fetch data + const { data, error: fetchError } = await supabase + .from('items') + .select('*') + + if (fetchError) { + console.error('Database error:', fetchError) + return NextResponse.json( + { error: 'Failed to fetch data' }, + { status: 500 } + ) + } + + return NextResponse.json({ data }) + } catch (error) { + console.error('Unexpected error:', error) + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 } + ) + } +} +``` + +## Pattern 12: Rate Limiting with Auth + +Combine auth with rate limiting. + +```typescript +// lib/rate-limit.ts +import { Ratelimit } from '@upstash/ratelimit' +import { Redis } from '@upstash/redis' + +const ratelimit = new Ratelimit({ + redis: Redis.fromEnv(), + limiter: Ratelimit.slidingWindow(10, '10 s'), +}) + +export async function checkRateLimit(identifier: string) { + const { success, limit, reset, remaining } = await ratelimit.limit( + identifier + ) + + return { + success, + limit, + reset, + remaining, + } +} + +// Usage in API route +export async function POST(request: Request) { + const supabase = createServerClient() + const { data: { user } } = await supabase.auth.getUser() + + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + // Rate limit by user ID + const rateLimitResult = await checkRateLimit(user.id) + + if (!rateLimitResult.success) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': rateLimitResult.limit.toString(), + 'X-RateLimit-Remaining': rateLimitResult.remaining.toString(), + 'X-RateLimit-Reset': rateLimitResult.reset.toString(), + }, + } + ) + } + + // Process request + return NextResponse.json({ success: true }) +} +``` + +## Testing Auth Protection + +Always test auth protection: + +```typescript +// tests/auth.test.ts +import { describe, it, expect } from 'vitest' +import { GET } from '@/app/api/protected/route' + +describe('Auth Protection', () => { + it('rejects unauthenticated requests', async () => { + const request = new Request('http://localhost/api/protected') + const response = await GET(request) + + expect(response.status).toBe(401) + const json = await response.json() + expect(json.error).toBe('Unauthorized') + }) + + it('allows authenticated requests', async () => { + // Mock authenticated request + const request = new Request('http://localhost/api/protected', { + headers: { + Authorization: 'Bearer valid-token', + }, + }) + + const response = await GET(request) + expect(response.status).toBe(200) + }) +}) +``` + +## Security Best Practices + +1. **Always validate on server**: Never trust client-side auth +2. **Check ownership**: Verify user owns resource before modification +3. **Use RLS**: Combine with Row-Level Security in database +4. **Rate limit**: Prevent abuse of authenticated endpoints +5. **Audit log**: Log authentication failures and privileged actions +6. **Expire sessions**: Implement session timeouts +7. **HTTPS only**: Never send auth tokens over HTTP +8. **CSRF protection**: Use CSRF tokens for state-changing operations