# Authorization Model Defined

- **Pattern:** `ab-002245` (`saas-authorization.access-control.auth-model-defined`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002245
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002245)

## Why it matters

Ad-hoc role checks scattered across route files — `user.role === 'admin'` repeated in a dozen places — are impossible to audit and trivially out of sync. When a new role is added or a permission is tightened, every scattered comparison must be found and updated manually. One missed file is a silent privilege escalation. CWE-284 (Improper Access Control) and OWASP A01 (Broken Access Control) both trace back to exactly this pattern: authorization decisions made without a single, auditable source of truth. NIST 800-53 AC-3 requires access enforcement to be consistently applied — string comparisons in individual handlers fail that standard by design.

## Severity rationale

High because a missing or stale inline role check grants unauthorized capabilities to any authenticated user who reaches that route.

## Remediation

Create `lib/auth/permissions.ts` as the single source of truth for all role definitions and permission checks. Export a typed `hasPermission(user, action)` helper and import it from every route handler — never compare role strings inline again.

```typescript
// lib/auth/permissions.ts
export const ROLES = { ADMIN: 'admin', MEMBER: 'member', VIEWER: 'viewer' } as const;
export type Role = typeof ROLES[keyof typeof ROLES];

export function hasPermission(user: { role: Role }, action: string): boolean {
  const permissions: Record<Role, string[]> = {
    admin: ['read', 'write', 'delete', 'manage_users'],
    member: ['read', 'write'],
    viewer: ['read'],
  };
  return permissions[user.role]?.includes(action) ?? false;
}
```

After adding this file, grep for `user.role ===` across the codebase and replace every hit with a `hasPermission()` call.

## Detection

- **ID:** `auth-model-defined`
- **Severity:** `high`
- **What to look for:** Count all relevant instances and enumerate each. Before evaluating, extract and quote any relevant configuration or UI text found. Look for a central authorization configuration file (e.g., `lib/auth/permissions.ts`, `config/roles.ts`), usage of RBAC libraries (casbin, accesscontrol, permit.io, casl), or a defined TypeScript `enum` or `const` object for roles and permissions. Check whether API routes and middleware import from a single source for authorization decisions rather than comparing raw strings inline.
- **Pass criteria:** A centralized, documented authorization model exists — a specific file or library defines all roles and their permissions, and route handlers reference it rather than using ad-hoc string comparisons. At least 1 implementation must be verified. A partial or placeholder implementation does not count as pass.
- **Fail criteria:** Authorization logic is purely ad hoc — raw string comparisons like `user.role === 'admin'` or `user.isAdmin === true` are scattered across route files with no central definition source.
- **Skip (N/A) when:** Project has no authentication system detected (no auth library in dependencies, no session handling, no user concept in the data model).
- **Detail on fail:** `"Authorization logic is ad hoc. Found inline role comparisons in multiple route files with no central permissions definition."` (Include the count of affected files if known.)
- **Remediation:** Define a central source of truth for authorization. Create a `lib/auth/permissions.ts` file that exports your role definitions and a `hasPermission(user, action)` helper function. All route handlers should import from this file rather than comparing raw strings. This makes it easy to audit who can do what and ensures changes propagate everywhere.

  ```typescript
  // lib/auth/permissions.ts
  export const ROLES = { ADMIN: 'admin', MEMBER: 'member', VIEWER: 'viewer' } as const;
  export type Role = typeof ROLES[keyof typeof ROLES];

  export function hasPermission(user: { role: Role }, action: string): boolean {
    const permissions: Record<Role, string[]> = {
      admin: ['read', 'write', 'delete', 'manage_users'],
      member: ['read', 'write'],
      viewer: ['read'],
    };
    return permissions[user.role]?.includes(action) ?? false;
  }
  ```

  After the change, verify by searching for raw role string comparisons (`user.role === `) and converting them to `hasPermission()` calls.

## External references

- cwe CWE-284
- cwe CWE-285
- owasp A01
- nist AC-3

Taxons: access-control

HTML version: https://auditbuffet.com/patterns/ab-002245
