Authentication Patterns
AuthenticationSecuritySupabaseRole-based Access ControlSession Management
Description
Guidelines for data-auth
Globs
**/*
---
description: Guidelines for data-auth
globs: **/*
---
# Authentication Patterns
## AI Guidelines
Implement secure authentication using Supabase Auth with proper session management, role-based access control, and tenant isolation. Create middleware for route protection, validate user identity in all actions, and follow secure token handling practices. Ensure consistent error handling for authentication failures.
## Key Patterns
### Authentication Flow
- **Supabase Auth**: Use Supabase for all authentication
- **Email/Password**: Primary authentication method
- **Social Auth**: Optional OAuth providers (Google, GitHub)
- **Magic Link**: Passwordless email authentication
- **Multi-tenant**: User associated with specific tenant
- **Role Assignment**: Role-based access control
### Session Management
- **Token Storage**: Secure cookie-based token storage
- **Session Refresh**: Automatic token refresh mechanism
- **Session Validation**: Middleware validation on all protected routes
- **Session Termination**: Proper logout handling
### User Context
- **Authentication Check**: `getUser()` helper for server actions
- **User Provider**: Client-side user context provider
- **Permission Helpers**: Role-based permission checking
### Tenant Isolation
- **Tenant Identifier**: Use `tenant_id` in all user operations
- **Row-Level Security**: Supabase RLS policies for tenant isolation
- **Default Tenant**: First-time login tenant assignment
- **Cross-Tenant Prevention**: Block cross-tenant access attempts
## Examples
### Authentication Implementation
```typescript
// In /src/lib/supabase/auth.ts
import { createClient } from '@/lib/supabase/supabase-server';
import { cookies } from 'next/headers';
export async function signIn(email: string, password: string): Promise<DbResponse> {
try {
const supabase = createClient(cookies());
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
});
if (error) {
return { success: false, error: error.message };
}
// Fetch user profile with tenant and role info
const { data: profile, error: profileError } = await supabase
.from('profiles')
.select('*')
.eq('id', data.user.id)
.single();
if (profileError) {
return { success: false, error: 'Failed to fetch user profile' };
}
return {
success: true,
data: {
id: data.user.id,
email: data.user.email!,
name: profile.name,
tenant_id: profile.tenant_id,
role: profile.role
}
};
} catch (error) {
console.error('Error in signIn:', error);
return { success: false, error: 'Authentication failed' };
}
}
export async function signOut(): Promise<DbResponse> {
try {
const supabase = createClient(cookies());
const { error } = await supabase.auth.signOut();
if (error) {
return { success: false, error: error.message };
}
return { success: true };
} catch (error) {
console.error('Error in signOut:', error);
return { success: false, error: 'Failed to sign out' };
}
}
export async function getCurrentUser(): Promise<DbResponse> {
try {
const supabase = createClient(cookies());
const { data: { session }, error: sessionError } = await supabase.auth.getSession();
if (sessionError || !session) {
return { success: false, error: 'No active session' };
}
const { data: profile, error: profileError } = await supabase
.from('profiles')
.select('*')
.eq('id', session.user.id)
.single();
if (profileError) {
return { success: false, error: 'Failed to fetch user profile' };
}
return {
success: true,
data: {
id: session.user.id,
email: session.user.email!,
name: profile.name,
tenant_id: profile.tenant_id,
role: profile.role
}
};
} catch (error) {
console.error('Error in getCurrentUser:', error);
return { success: false, error: 'Failed to get current user' };
}
}
```
### Server-Side Authentication Action
```typescript
// In /src/app/actions/user.ts
'use server';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { getCurrentUser } from '@/lib/supabase/auth';
import { serverCache } from '@/lib/cache';
export async function getUser(): Promise<ActionResult> {
try {
// Check cache first (short TTL for security)
const cacheKey = 'current_user';
const cachedUser = serverCache.get(cacheKey);
if (cachedUser) {
return { success: true, data: cachedUser };
}
// Get user from Supabase
const result = await getCurrentUser();
if (!result.success) {
return { success: false, error: 'Authentication required' };
}
// Cache user data (short TTL)
serverCache.set(cacheKey, result.data, { ttl: 60 }); // 60 seconds
return { success: true, data: result.data };
} catch (error) {
console.error('Error in getUser:', error);
return { success: false, error: 'Failed to authenticate user' };
}
}
export async function requireAuth(redirectTo: string = '/login') {
const userResult = await getUser();
if (!userResult.success) {
// If client-side, return data for client-side redirect
if (typeof window !== 'undefined') {
return { authenticated: false, redirectUrl: redirectTo };
}
// If server-side, redirect immediately
redirect(redirectTo);
}
return { authenticated: true, user: userResult.data };
}
export async function requireRole(
requiredRoles: string[],
redirectTo: string = '/unauthorized'
) {
const { authenticated, user, redirectUrl } = await requireAuth();
if (!authenticated) {
return { authorized: false, redirectUrl };
}
if (!requiredRoles.includes(user.role)) {
// If client-side, return data for client-side redirect
if (typeof window !== 'undefined') {
return { authorized: false, redirectUrl: redirectTo };
}
// If server-side, redirect immediately
redirect(redirectTo);
}
return { authorized: true, user };
}
```
### Authentication Middleware
```typescript
// In /src/middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/supabase-server';
export async function middleware(request: NextRequest) {
// Get the pathname of the request
const { pathname } = request.nextUrl;
// Skip authentication check for public routes
if (
pathname.startsWith('/_next') ||
pathname.startsWith('/api/auth') ||
pathname === '/login' ||
pathname === '/register' ||
pathname === '/forgot-password'
) {
return NextResponse.next();
}
// Create supabase server client
const supabase = createClient();
// Check if user is authenticated
const { data: { session }, error } = await supabase.auth.getSession();
// If no session or error, redirect to login
if (error || !session) {
const url = new URL('/login', request.url);
url.searchParams.set('redirect', pathname);
return NextResponse.redirect(url);
}
// For tenant-specific paths, verify tenant access
if (pathname.includes('/[tenant]/')) {
// Extract tenant from URL (based on your URL structure)
const tenantMatch = pathname.match(/\/([^\/]+)\/dashboard/);
if (tenantMatch && tenantMatch[1]) {
const requestedTenant = tenantMatch[1];
// Fetch user's tenant
const { data: profile, error: profileError } = await supabase
.from('profiles')
.select('tenant_id')
.eq('id', session.user.id)
.single();
// If error or tenant mismatch, redirect to unauthorized
if (profileError || profile.tenant_id !== requestedTenant) {
return NextResponse.redirect(new URL('/unauthorized', request.url));
}
}
}
// Continue with the request
return NextResponse.next();
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)']
};
```
### Client-Side Authentication
```typescript
// In /src/context/auth/AuthProvider.tsx
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { createClient } from '@/lib/supabase/supabase-browser';
import { useRouter } from 'next/navigation';
interface AuthContextValue {
user: AuthUser | null;
loading: boolean;
signIn: (email: string, password: string) => Promise<ActionResult>;
signOut: () => Promise;
isRole: (role: string | string[]) => boolean;
}
const AuthContext = createContext(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const router = useRouter();
const supabase = createClient();
useEffect(() => {
// Fetch initial session
const initAuth = async () => {
setLoading(true);
try {
const { data: { session } } = await supabase.auth.getSession();
if (session) {
// Fetch user profile
const { data } = await supabase
.from('profiles')
.select('*')
.eq('id', session.user.id)
.single();
setUser({
id: session.user.id,
email: session.user.email!,
name: data.name,
tenant_id: data.tenant_id,
role: data.role
});
}
} catch (error) {
console.error('Error initializing auth:', error);
} finally {
setLoading(false);
}
};
initAuth();
// Set up auth state change listener
const { data: { subscription } } = supabase.auth.onAuthStateChange(
async (event, session) => {
if (event === 'SIGNED_IN' && session) {
// Fetch user profile
const { data } = await supabase
.from('profiles')
.select('*')
.eq('id', session.user.id)
.single();
setUser({
id: session.user.id,
email: session.user.email!,
name: data.name,
tenant_id: data.tenant_id,
role: data.role
});
} else if (event === 'SIGNED_OUT') {
setUser(null);
router.push('/login');
}
}
);
return () => {
subscription.unsubscribe();
};
}, [supabase, router]);
const signIn = async (email: string, password: string) => {
try {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
});
if (error) {
return { success: false, error: error.message };
}
// Fetch user profile
const { data: profile, error: profileError } = await supabase
.from('profiles')
.select('*')
.eq('id', data.user.id)
.single();
if (profileError) {
return { success: false, error: 'Failed to fetch user profile' };
}
const user = {
id: data.user.id,
email: data.user.email!,
name: profile.name,
tenant_id: profile.tenant_id,
role: profile.role
};
setUser(user);
return { success: true, data: user };
} catch (error) {
console.error('Error signing in:', error);
return { success: false, error: 'Authentication failed' };
}
};
const signOut = async () => {
await supabase.auth.signOut();
setUser(null);
router.push('/login');
};
const isRole = (roleOrRoles: string | string[]) => {
if (!user) return false;
const roles = Array.isArray(roleOrRoles) ? roleOrRoles : [roleOrRoles];
return roles.includes(user.role);
};
return (
{children}
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
```
## Security Best Practices
1. **Token Management**
- Store tokens only in HTTPOnly, Secure cookies
- Never store tokens in localStorage
- Implement short token expiration times
- Use refresh tokens for longer sessions
2. **Password Security**
- Enforce strong password policies
- Implement rate limiting for login attempts
- Use Supabase's password hashing (Argon2)
- Support two-factor authentication
3. **Multi-Tenancy**
- Associate users with specific tenants
- Use RLS policies based on tenant_id
- Validate tenant access in middleware
- Prevent tenant ID manipulation
4. **Permission Management**
- Implement granular RBAC permissions
- Check permissions in all actions
- Keep permission checks consistent
- Log access violations
## Related Rules
- core-architecture.mdc - Three-layer architecture
- api-design.mdc - API design patterns
- data-supabase.mdc - Database access patterns
- api-implementation.mdc - API implementation
- ui-state.mdc - State management