Authentication & Authorization
User authentication, authorization patterns, and access control mechanisms for Smart Shelf.
Authentication & Authorization
Comprehensive authentication and authorization system for Smart Shelf, implementing secure user access patterns, role-based permissions, and multi-layer authorization mechanisms.
Authentication Architecture
Authentication Flow
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │ │ Server │ │ Supabase │
│ │ │ │ │ Auth │
├─────────────┤ ├─────────────┤ ├─────────────┤
│1. Login │───▶│2. Validate │───▶│3. Generate │
│ Request │ │ Creds │ │ JWT Token │
│ │◄───│4. Return │◄───│ │
│ │ │ Token │ │ │
│5. Store │ │ │ │ │
│ Token │ │ │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
JWT Token Structure
// JWT Payload Structure
interface JWTPayload {
// Standard claims
sub: string; // User ID
iss: string; // Issuer (Supabase)
aud: string; // Audience
exp: number; // Expiration timestamp
iat: number; // Issued at timestamp
// Custom claims
email: string; // User email
role: string; // User role
warehouse_id?: string; // Default warehouse
permissions: string[]; // User permissions
session_id: string; // Session identifier
}
Server-Side Authentication
Authentication Utilities
// lib/auth/server.ts
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export async function getCurrentUser() {
const supabase = createServerComponentClient({ cookies });
try {
const { data: { session }, error } = await supabase.auth.getSession();
if (error || !session) {
return null;
}
// Get user profile with permissions
const { data: profile } = await supabase
.from('user_profiles')
.select('*, user_roles(role, permissions)')
.eq('id', session.user.id)
.single();
return {
id: session.user.id,
email: session.user.email,
...profile,
};
} catch (error) {
console.error('Authentication error:', error);
return null;
}
}
export async function requireAuth() {
const user = await getCurrentUser();
if (!user) {
redirect('/auth/login');
}
return user;
}
export async function validateAuth(req: Request): Promise<User> {
const authHeader = req.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
throw new Error('Invalid authorization header');
}
const token = authHeader.substring(7);
try {
const payload = await verifyJWT(token);
const user = await getUserById(payload.sub);
if (!user?.is_active) {
throw new Error('User account is inactive');
}
return user;
} catch (error) {
throw new Error('Invalid or expired token');
}
}
Session Management
// lib/auth/session.ts
export class SessionManager {
private static readonly SESSION_DURATION = 24 * 60 * 60 * 1000; // 24 hours
private static readonly REFRESH_THRESHOLD = 60 * 60 * 1000; // 1 hour
static async refreshSession(user: User): Promise<Session | null> {
const supabase = createServerComponentClient({ cookies });
try {
const { data, error } = await supabase.auth.refreshSession();
if (error || !data.session) {
throw new Error('Failed to refresh session');
}
return data.session;
} catch (error) {
console.error('Session refresh error:', error);
return null;
}
}
static shouldRefreshSession(session: Session): boolean {
const expiresAt = session.expires_at * 1000;
const now = Date.now();
const timeUntilExpiry = expiresAt - now;
return timeUntilExpiry <= this.REFRESH_THRESHOLD;
}
static async validateSession(sessionId: string): Promise<boolean> {
const supabase = createServerComponentClient({ cookies });
const { data, error } = await supabase
.from('user_sessions')
.select('id, expires_at, is_active')
.eq('id', sessionId)
.single();
if (error || !data?.is_active) {
return false;
}
return new Date(data.expires_at) > new Date();
}
}
Client-Side Authentication
Authentication Hook
// lib/auth/client.ts
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
export function useAuth() {
const supabase = createClientComponentClient();
const router = useRouter();
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const getSession = async () => {
const { data: { session } } = await supabase.auth.getSession();
if (session?.user) {
const { data: profile } = await supabase
.from('user_profiles')
.select('*')
.eq('id', session.user.id)
.single();
setUser({
id: session.user.id,
email: session.user.email,
...profile,
});
}
setLoading(false);
};
getSession();
const { data: { subscription } } = supabase.auth.onAuthStateChange(
async (event, session) => {
if (event === 'SIGNED_OUT' || !session) {
setUser(null);
} else if (event === 'SIGNED_IN' && session) {
const { data: profile } = await supabase
.from('user_profiles')
.select('*')
.eq('id', session.user.id)
.single();
setUser({
id: session.user.id,
email: session.user.email,
...profile,
});
}
}
);
return () => subscription.unsubscribe();
}, [supabase]);
const signIn = async (email: string, password: string) => {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
throw new Error(error.message);
}
router.push('/dashboard');
return data;
};
const signOut = async () => {
await supabase.auth.signOut();
setUser(null);
router.push('/auth/login');
};
const signUp = async (email: string, password: string, userData: any) => {
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
data: userData,
},
});
if (error) {
throw new Error(error.message);
}
return data;
};
return {
user,
loading,
signIn,
signOut,
signUp,
};
}
Role-Based Access Control (RBAC)
Role Definitions
// lib/auth/roles.ts
export enum UserRole {
ADMIN = 'admin',
MANAGER = 'manager',
STAFF = 'staff',
VIEWER = 'viewer',
}
export const ROLE_HIERARCHY = {
[UserRole.ADMIN]: 4,
[UserRole.MANAGER]: 3,
[UserRole.STAFF]: 2,
[UserRole.VIEWER]: 1,
};
export const PERMISSIONS = {
// Product Management
'products:read': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF, UserRole.VIEWER],
'products:create': [UserRole.ADMIN, UserRole.MANAGER],
'products:update': [UserRole.ADMIN, UserRole.MANAGER],
'products:delete': [UserRole.ADMIN],
// Inventory Management
'inventory:read': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF, UserRole.VIEWER],
'inventory:adjust': [UserRole.ADMIN, UserRole.MANAGER],
'inventory:transfer': [UserRole.ADMIN, UserRole.MANAGER],
'inventory:count': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF],
// Order Management
'orders:read': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF, UserRole.VIEWER],
'orders:create': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF],
'orders:update': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF],
'orders:approve': [UserRole.ADMIN, UserRole.MANAGER],
'orders:cancel': [UserRole.ADMIN, UserRole.MANAGER],
// Financial
'financial:read': [UserRole.ADMIN, UserRole.MANAGER, UserRole.VIEWER],
'financial:reports': [UserRole.ADMIN, UserRole.MANAGER],
// User Management
'users:read': [UserRole.ADMIN],
'users:create': [UserRole.ADMIN],
'users:update': [UserRole.ADMIN],
'users:delete': [UserRole.ADMIN],
// System Administration
'system:settings': [UserRole.ADMIN],
'system:backup': [UserRole.ADMIN],
'system:audit': [UserRole.ADMIN],
} as const;
export function hasPermission(userRole: UserRole, permission: string): boolean {
const allowedRoles = PERMISSIONS[permission as keyof typeof PERMISSIONS];
return allowedRoles?.includes(userRole) ?? false;
}
export function requirePermission(user: User, permission: string): void {
if (!hasPermission(user.role, permission)) {
throw new Error(`Access denied: ${permission} permission required`);
}
}
export function hasHigherRole(userRole: UserRole, targetRole: UserRole): boolean {
return ROLE_HIERARCHY[userRole] > ROLE_HIERARCHY[targetRole];
}
Authorization Guards
Component-Level Authorization
// components/auth/role-guard.tsx
interface RoleGuardProps {
allowedRoles: UserRole[];
children: React.ReactNode;
fallback?: React.ReactNode;
}
export function RoleGuard({ allowedRoles, children, fallback }: RoleGuardProps) {
const { user } = useAuth();
if (!user || !allowedRoles.includes(user.role)) {
return fallback || <UnauthorizedMessage />;
}
return <>{children}</>;
}
// Permission-based guard
interface PermissionGuardProps {
permission: string;
children: React.ReactNode;
fallback?: React.ReactNode;
}
export function PermissionGuard({ permission, children, fallback }: PermissionGuardProps) {
const { user } = useAuth();
if (!user || !hasPermission(user.role, permission)) {
return fallback || null;
}
return <>{children}</>;
}
// Usage examples
<RoleGuard allowedRoles={[UserRole.ADMIN, UserRole.MANAGER]}>
<AdminPanel />
</RoleGuard>
<PermissionGuard permission="products:create">
<CreateProductButton />
</PermissionGuard>
API Route Authorization
// lib/auth/api-auth.ts
export function withAuth(
handler: (req: NextRequest, user: User) => Promise<NextResponse>,
requiredPermission?: string
) {
return async function(req: NextRequest) {
try {
// Validate authentication
const user = await validateAuth(req);
// Check permission if required
if (requiredPermission) {
requirePermission(user, requiredPermission);
}
return handler(req, user);
} catch (error) {
if (error.message.includes('token')) {
return NextResponse.json(
{ success: false, error: { code: 'UNAUTHORIZED', message: 'Invalid token' } },
{ status: 401 }
);
}
if (error.message.includes('Access denied')) {
return NextResponse.json(
{ success: false, error: { code: 'FORBIDDEN', message: error.message } },
{ status: 403 }
);
}
throw error;
}
};
}
// Usage in API routes
export const GET = withAuth(async (req, user) => {
// Handler with authenticated user
const products = await getProducts(user);
return NextResponse.json({ success: true, data: products });
}, 'products:read');
export const POST = withAuth(async (req, user) => {
const body = await req.json();
const product = await createProduct(body, user);
return NextResponse.json({ success: true, data: product });
}, 'products:create');
Multi-Factor Authentication (MFA)
TOTP Implementation
// lib/auth/mfa.ts
export class MFAManager {
static async enableTOTP(userId: string): Promise<{ secret: string; qrCode: string }> {
const supabase = createServerComponentClient({ cookies });
const { data, error } = await supabase.auth.mfa.enroll({
factorType: 'totp',
});
if (error) {
throw new Error('Failed to enable MFA');
}
return {
secret: data.totp.secret,
qrCode: data.totp.qr_code,
};
}
static async verifyTOTP(factorId: string, token: string): Promise<boolean> {
const supabase = createServerComponentClient({ cookies });
const { data, error } = await supabase.auth.mfa.verify({
factorId,
code: token,
});
return !error && data.valid;
}
static async requireMFA(user: User): Promise<boolean> {
// Check if user requires MFA based on role or settings
return user.role === UserRole.ADMIN || user.mfa_enabled;
}
}
This authentication and authorization system provides comprehensive security controls while maintaining usability and scalability for Smart Shelf.