RBAC and ABAC: Role-Based and Attribute-Based Access Control Implementation
Vibe Prompt
"Design a comprehensive RBAC system with three distinct roles: admin, editor, and viewer. The admin role should have full system management capabilities, the editor role should be able to create and modify content, and the viewer role should only have read-only access. Additionally, implement attribute-based policies that restrict certain actions based on user attributes like department, time of day, or device type."
RBAC Implementation: The Foundation of Role-Based Security
Role-Based Access Control (RBAC) is a fundamental security model that assigns permissions to users based on their organizational roles. This approach simplifies permission management by grouping users into roles and assigning permissions to those roles rather than to individual users.
Core RBAC Components
The RBAC model consists of four key components:
- Users: Individual accounts that require access to system resources
- Roles: Job functions or responsibilities that define a set of permissions
- Permissions: Authorized actions that can be performed on system resources
- Sessions: Active user contexts that establish the connection between users and roles
RBAC Implementation in TypeScript
// Role definitions with granular permissions
const ROLES = {
admin: ['read', 'write', 'delete', 'manage_users', 'configure_system', 'audit_logs'],
editor: ['read', 'write', 'publish_content', 'manage_drafts'],
viewer: ['read'],
moderator: ['read', 'flag_content', 'ban_users'],
developer: ['read', 'write', 'deploy_code', 'manage_environments'],
} as const;
// Permission validation function
function authorize(user: { role: keyof typeof ROLES }, requiredPermission: string): boolean {
const permissions = ROLES[user.role] || [];
return permissions.includes(requiredPermission as any);
}
// Express.js middleware for permission enforcement
function requirePermission(permission: string) {
return (req: Request, res: Response, next: NextFunction) => {
// Validate user authentication first
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
// Check if user has the required permission
if (!authorize(req.user, permission)) {
return res.status(403).json({
error: 'Insufficient permissions',
required: permission,
userRole: req.user.role
});
}
// Permission granted, proceed to next middleware/handler
next();
};
}
// Route protection with role-based access control
app.get('/api/users', requirePermission('manage_users'), (req, res) => {
res.json(users);
});
app.post('/api/content', requirePermission('write'), createContentHandler);
app.delete('/api/content/:id', requirePermission('delete'), deleteContentHandler);
// Role hierarchy implementation
const ROLE_HIERARCHY: Record<string, string[]> = {
admin: ['editor', 'viewer', 'moderator', 'developer'],
editor: ['viewer'],
moderator: ['viewer'],
developer: ['viewer'],
viewer: []
};
function hasRoleOrHigher(userRole: string, requiredRole: string): boolean {
if (userRole === requiredRole) return true;
return ROLE_HIERARCHY[userRole]?.includes(requiredRole) || false;
}
Advanced RBAC Patterns
Role-Based Middleware with Error Handling
// Custom error classes for better error handling
class ForbiddenError extends Error {
constructor(message: string = 'Access denied') {
super(message);
this.name = 'ForbiddenError';
}
}
class UnauthorizedError extends Error {
constructor(message: string = 'Authentication required') {
super(message);
this.name = 'UnauthorizedError';
}
}
// Enhanced authorization middleware with logging
function requirePermissionWithLogging(permission: string) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
// Log access attempt for security auditing
console.log(`Access attempt: ${req.method} ${req.path} by user ${req.user?.id} with role ${req.user?.role}`);
if (!req.user) {
throw new UnauthorizedError();
}
if (!authorize(req.user, permission)) {
// Log unauthorized access attempt
console.warn(`Unauthorized access attempt: User ${req.user.id} attempted ${permission}`);
throw new ForbiddenError(`Permission '${permission}' is required`);
}
next();
} catch (error) {
if (error instanceof ForbiddenError || error instanceof UnauthorizedError) {
return res.status(error instanceof UnauthorizedError ? 401 : 403).json({
error: error.message,
timestamp: new Date().toISOString()
});
}
next(error);
}
};
}
ABAC Implementation: Dynamic Attribute-Based Access Control
Attribute-Based Access Control (ABAC) provides more granular and flexible access control by evaluating attributes of users, resources, actions, and environmental conditions. This approach is essential for complex systems where role-based models fall short.
ABAC Policy Engine Implementation
// User and resource type definitions
interface User {
id: string;
role: string;
department: string;
clearanceLevel: number;
isActive: boolean;
lastLogin: Date;
}
interface Document {
id: string;
authorId: string;
department: string;
sensitivity: 'public' | 'internal' | 'confidential' | 'restricted';
ownerId: string;
createdAt: Date;
status: 'draft' | 'published' | 'archived';
}
// ABAC Policy Engine
class PolicyEngine {
// Policy: Users can only edit their own articles unless they are admin
canEditArticle(user: User, article: Document): boolean {
// Admin override - admins can edit any article
if (user.role === 'admin') return true;
// Owner can edit their own articles
if (article.authorId === user.id) return true;
// Department heads can edit department articles
if (user.department === article.department && user.clearanceLevel >= 3) return true;
return false;
}
// Policy: Only admins can delete articles during business hours
canDelete(user: User, currentTime: Date = new Date()): boolean {
const hour = currentTime.getHours();
const isBusinessHours = hour >= 9 && hour <= 18;
if (user.role === 'admin' && isBusinessHours) return true;
// Emergency delete allowed for senior staff anytime
if (user.clearanceLevel >= 4) return true;
return false;
}
// Policy: Time-based access restrictions
canAccessDuringMaintenance(user: User, maintenanceWindow: { start: Date, end: Date }): boolean {
const now = new Date();
const isMaintenance = now >= maintenanceWindow.start && now <= maintenanceWindow.end;
if (isMaintenance) {
// Only admins and developers can access during maintenance
return user.role === 'admin' || user.role === 'developer';
}
return true;
}
// Policy: Device-based access control
canAccessFromDevice(user: User, deviceType: 'mobile' | 'desktop' | 'tablet', location: string): boolean {
// Mobile access restrictions
if (deviceType === 'mobile') {
// Only allow mobile access for certain roles
return user.role === 'admin' || user.role === 'editor';
}
// Location-based restrictions
if (location === 'external') {
// External access only for specific roles
return user.role === 'admin' || user.role === 'developer';
}
return true;
}
// Complex policy: Multi-factor authorization
canPerformSensitiveAction(user: User, action: string, resource: any, context: any): boolean {
// Check basic role permissions
if (!this.hasBasicPermission(user, action)) return false;
// Check time-based restrictions
if (!this.isWithinAllowedTime(user, action)) return false;
// Check resource-specific policies
if (!this.evaluateResourcePolicy(user, action, resource)) return false;
// Check environmental conditions
if (!this.evaluateEnvironment(user, context)) return false;
return true;
}
private hasBasicPermission(user: User, action: string): boolean {
const permissions = ROLES[user.role as keyof typeof ROLES] || [];
return permissions.includes(action as any);
}
private isWithinAllowedTime(user: User, action: string): boolean {
const hour = new Date().getHours();
const restrictedActions = ['delete', 'configure_system', 'manage_users'];
if (restrictedActions.includes(action)) {
return hour >= 8 && hour <= 18;
}
return true;
}
private evaluateResourcePolicy(user: User, action: string, resource: any): boolean {
// Resource-specific logic
if (resource.type === 'financial_data' && action === 'read') {
return user.clearanceLevel >= 4;
}
if (resource.type === 'user_data' && action === 'delete') {
return user.role === 'admin';
}
return true;
}
private evaluateEnvironment(user: User, context: any): boolean {
// Environmental checks
if (context.ipAddress && this.isSuspiciousIP(context.ipAddress)) {
return false;
}
if (context.deviceTrusted === false) {
return user.clearanceLevel >= 3;
}
return true;
}
private isSuspiciousIP(ip: string): boolean {
// Implement IP reputation checking
const suspiciousRanges = ['192.168.1.', '10.0.0.'];
return suspiciousRanges.some(range => ip.startsWith(range));
}
}
// Usage example
const policyEngine = new PolicyEngine();
// Check if user can edit an article
const canEdit = policyEngine.canEditArticle(user, article);
// Check if user can delete with time constraints
const canDelete = policyEngine.canDelete(user, new Date());
Supabase Row Level Security (RLS): Database-Level Access Control
Row Level Security (RLS) provides an additional security layer by enforcing access control at the database level. This ensures that even if application-level controls fail, the database will still prevent unauthorized data access.
Supabase RLS Implementation
-- Enable RLS on the orders table
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- Policy: Users can view their own orders
CREATE POLICY "Users can view own orders"
ON orders FOR SELECT
USING (auth.uid() = user_id);
-- Policy: Admins can view all orders
CREATE POLICY "Admins can view all orders"
ON orders FOR SELECT
USING (auth.jwt() ->> 'role' = 'admin');
-- Policy: Users can insert orders for themselves
CREATE POLICY "Users can create their own orders"
ON orders FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- Policy: Admins can update any order
CREATE POLICY "Admins can update any order"
ON orders FOR UPDATE
USING (auth.jwt() ->> 'role' = 'admin');
-- Policy: Users can update their own orders only if not completed
CREATE POLICY "Users can update their own pending orders"
ON orders FOR UPDATE
USING (
auth.uid() = user_id
AND status = 'pending'
);
-- Policy: Users can delete their own orders only if not shipped
CREATE POLICY "Users can delete their own unshipped orders"
ON orders FOR DELETE
USING (
auth.uid() = user_id
AND status != 'shipped'
);
-- Create a policy for department-based access
CREATE POLICY "Department staff can view department orders"
ON orders FOR SELECT
USING (
auth.jwt() ->> 'department' = (SELECT department FROM users WHERE id = auth.uid())
AND status IN ('pending', 'processing')
);
-- Create a policy for time-based restrictions
CREATE POLICY "No modifications after business hours"
ON orders FOR UPDATE
USING (
EXTRACT(HOUR FROM CURRENT_TIME) >= 9
AND EXTRACT(HOUR FROM CURRENT_TIME) <= 18
AND (auth.jwt() ->> 'role' = 'admin' OR auth.uid() = user_id)
);
-- Create a policy for sensitive data access
CREATE POLICY "Only finance team can view financial details"
ON orders FOR SELECT
USING (
auth.jwt() ->> 'department' = 'finance'
OR auth.jwt() ->> 'role' = 'admin'
);
-- Create a policy for audit trail
CREATE POLICY "Audit log access for admins only"
ON audit_logs FOR SELECT
USING (auth.jwt() ->> 'role' = 'admin');
Advanced Supabase RLS Patterns
-- Create a function to check user permissions
CREATE OR REPLACE FUNCTION check_user_permission(permission TEXT)
RETURNS BOOLEAN AS $$
BEGIN
RETURN EXISTS (
SELECT 1 FROM auth.users
WHERE id = auth.uid()
AND (permissions @> ARRAY[permission] OR role = 'admin')
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Use the function in policies
CREATE POLICY "Users with write permission can update"
ON content FOR UPDATE
USING (check_user_permission('write'));
-- Create a policy with multiple conditions
CREATE POLICY "Complex access policy"
ON sensitive_data FOR ALL
USING (
auth.jwt() ->> 'role' = 'admin'
OR (
auth.jwt() ->> 'department' = 'security'
AND EXTRACT(HOUR FROM CURRENT_TIME) BETWEEN 8 AND 18
AND auth.uid() = user_id
)
);
Key Learning Points
RBAC Fundamentals
- Principle of Least Privilege: Users should only have the