Without role-based access control, every authenticated user has the same permissions as an administrator — a regular user can delete records, reassign roles, or exfiltrate all data through unguarded API endpoints. NIST 800-53 rev5 AC-2 (Account Management) requires formal role definitions, and AC-6 (Least Privilege) mandates that users receive only the minimum access required for their function. CWE-285 (Improper Authorization) and OWASP A01 (Broken Access Control) consistently rank this as the most exploited web application vulnerability. Frontend-only role gates are bypassed by any direct API call using the browser's developer tools.
High because absent server-side permission checks allow any authenticated user to perform privileged operations simply by calling the API directly, bypassing every UI-level guard.
Define roles and permissions in a central module, then enforce them in every sensitive API handler — never on the client alone.
// lib/rbac.ts
export const PERMISSIONS = {
admin: ['read', 'write', 'delete', 'manage_users'],
auditor: ['read', 'write'],
viewer: ['read']
} as const
export function requirePermission(
userRole: keyof typeof PERMISSIONS,
action: string
) {
if (!PERMISSIONS[userRole]?.includes(action)) {
throw new Response(null, { status: 403 })
}
}
// app/api/admin/users/route.ts
export const DELETE = async (req: Request) => {
const session = await getServerSession()
requirePermission(session.user.role, 'manage_users')
// ... proceed
}
Log every role assignment and escalation event per NIST AU-2 requirements. Run permission checks in src/middleware.ts for route-level protection in addition to handler-level checks.
ID: gov-fisma-fedramp.access-control.rbac-least-privilege
Severity: high
What to look for: Enumerate all role definitions in the codebase (admin, user, viewer, etc.). Count every API route that handles sensitive operations and for each classify whether it has a permission check. Look for middleware that enforces role-based access. Check whether users receive minimal permissions by default and whether role assignment is audited. Search for hardcoded role checks that suggest privilege creep.
Pass criteria: RBAC is implemented with at least 2 distinct roles. All sensitive API routes check user role/permissions before processing requests. Users start with minimal permissions; additional permissions require explicit assignment. Role changes are logged. Report the ratio of protected routes to total sensitive routes.
Fail criteria: No role/permission system found, or all authenticated users have the same permissions, or access control checks are missing from sensitive API routes. Do not pass when role checks exist only on the frontend but not enforced server-side.
Skip (N/A) when: The project has no user authentication or all users are read-only.
Detail on fail: Specify the RBAC gaps. Example: "No role definitions found in codebase. All authenticated users can access all API routes (POST /api/admin/*, PUT /api/settings/*, etc.) regardless of their role." or "User role is hardcoded as 'admin' in initial registration — no permission escalation required"
Remediation: Implement role-based access control. Define roles, assign permissions, and check them on protected routes:
// lib/roles.ts
export enum Role {
ADMIN = 'admin',
AUDITOR = 'auditor',
USER = 'user'
}
export const permissions: Record<Role, string[]> = {
[Role.ADMIN]: ['read', 'write', 'delete', 'manage_users', 'manage_roles'],
[Role.AUDITOR]: ['read', 'write', 'export'],
[Role.USER]: ['read', 'export']
}
// app/api/admin/users/route.ts
import { getServerSession } from 'next-auth/next'
export const POST = async (req: Request) => {
const session = await getServerSession()
const user = session?.user as any
if (!permissions[user.role]?.includes('manage_users')) {
return Response.json({ error: 'Unauthorized' }, { status: 403 })
}
// Process admin action...
}
Always verify permissions server-side, not client-side.