Authentication & Security
Authentication patterns, role-based access control, and security best practices
Authentication & Security
Implement secure authentication and authorization patterns using Supabase Auth and role-based access control.
Role-Based Access Control
Permission System
// lib/auth/permissions.ts
export type UserRole = 'admin' | 'manager' | 'employee' | 'viewer'
export const PERMISSIONS = {
admin: [
'users.create',
'users.read',
'users.update',
'users.delete',
'products.create',
'products.read',
'products.update',
'products.delete',
'inventory.create',
'inventory.read',
'inventory.update',
'inventory.delete',
'analytics.read',
'settings.update',
],
manager: [
'products.create',
'products.read',
'products.update',
'inventory.create',
'inventory.read',
'inventory.update',
'analytics.read',
],
employee: [
'products.read',
'inventory.read',
'inventory.update',
],
viewer: [
'products.read',
'inventory.read',
],
} as const
export function hasPermission(userRole: UserRole, permission: string): boolean {
return PERMISSIONS[userRole]?.includes(permission as any) || false
}
Server-Side Authentication
User Management
// lib/auth/server.ts
import { createClient } from '@/lib/supabase/server'
export async function getCurrentUser() {
const supabase = await createClient()
try {
const { data: { user }, error } = await supabase.auth.getUser()
if (error || !user) {
return null
}
// Get user profile with role
const { data: profile } = await supabase
.from('users')
.select('id, email, role, full_name')
.eq('id', user.id)
.single()
return {
...user,
profile
}
} catch (error) {
console.error('Error getting current user:', error)
return null
}
}
export async function requireAuth() {
const user = await getCurrentUser()
if (!user) {
throw new Error('Authentication required')
}
return user
}
export async function requireRole(requiredRole: UserRole) {
const user = await requireAuth()
if (!hasPermission(user.profile?.role, 'admin') && user.profile?.role !== requiredRole) {
throw new Error('Insufficient permissions')
}
return user
}
Client-Side Authentication
Auth Hook
// hooks/use-current-user.ts
'use client'
import { useEffect, useState } from 'react'
import { createClient } from '@/lib/supabase/client'
export function useCurrentUser() {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const supabase = createClient()
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null)
setLoading(false)
})
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null)
setLoading(false)
}
)
return () => subscription.unsubscribe()
}, [])
return { user, loading }
}
Protected Route Component
// components/auth/protected-route.tsx
'use client'
import { useCurrentUser } from '@/hooks/use-current-user'
import { redirect } from 'next/navigation'
import { useEffect } from 'react'
interface ProtectedRouteProps {
children: React.ReactNode
requiredRole?: UserRole
fallback?: React.ReactNode
}
export function ProtectedRoute({
children,
requiredRole,
fallback = <div>Loading...</div>
}: ProtectedRouteProps) {
const { user, loading } = useCurrentUser()
useEffect(() => {
if (!loading && !user) {
redirect('/auth/login')
}
}, [user, loading])
if (loading) {
return fallback
}
if (!user) {
return null
}
if (requiredRole && !hasPermission(user.profile?.role, requiredRole)) {
return <div>Access denied. Insufficient permissions.</div>
}
return <>{children}</>
}
Security Best Practices
Input Validation
Always validate and sanitize user input:
import { z } from 'zod'
const productSchema = z.object({
name: z.string().min(1).max(100),
sku: z.string().regex(/^[A-Z0-9-]+$/),
price: z.number().positive(),
})
export async function createProduct(formData: FormData) {
const rawData = {
name: formData.get('name'),
sku: formData.get('sku'),
price: Number(formData.get('price')),
}
const validatedData = productSchema.parse(rawData)
// Proceed with validated data
}
Environment Variables
Secure sensitive data with environment variables:
# .env.local
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-key
Row Level Security
Enable RLS policies in Supabase:
-- Enable RLS
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
-- Allow authenticated users to read products
CREATE POLICY "Users can read products"
ON products FOR SELECT
TO authenticated
USING (true);
-- Allow managers to modify products
CREATE POLICY "Managers can modify products"
ON products FOR ALL
TO authenticated
USING (
EXISTS (
SELECT 1 FROM users
WHERE users.id = auth.uid()
AND users.role IN ('admin', 'manager')
)
);
API Route Protection
Protect API routes with middleware:
// middleware.ts
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(req: NextRequest) {
const res = NextResponse.next()
const supabase = createMiddlewareClient({ req, res })
const {
data: { session },
} = await supabase.auth.getSession()
// Protect API routes
if (req.nextUrl.pathname.startsWith('/api/protected') && !session) {
return NextResponse.redirect(new URL('/auth/login', req.url))
}
return res
}
export const config = {
matcher: ['/api/protected/:path*', '/dashboard/:path*']
}
Authentication Flows
Login/Logout
// lib/auth/actions.ts
'use server'
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'
export async function signIn(formData: FormData) {
const email = formData.get('email') as string
const password = formData.get('password') as string
const supabase = await createClient()
const { error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) {
redirect('/auth/login?error=Invalid credentials')
}
redirect('/dashboard')
}
export async function signOut() {
const supabase = await createClient()
await supabase.auth.signOut()
redirect('/auth/login')
}
Session Management
Sessions are automatically managed by Supabase Auth. Use the provided hooks and utilities to check authentication state and manage user sessions across your application.