# Authentication & Authorization at the Framework Level (Part 2: Role/Permission Checks)

Part 1 covered resolving *who* the caller is, centrally, via middleware/guards. Part 2 covers the separate, per-operation question of *what that caller is allowed to do* — role-based and permission-based authorization enforced at the route or resource level.

## 1. Role-Based Authorization: Checking Against a Coarse Category

The simplest authorization model assigns each user one or more roles, and routes declare which roles may access them:

```ts
// NestJS: a decorator declares required roles, a guard reads and enforces it
const Roles = (...roles: string[]) => SetMetadata("roles", roles);

@Injectable()
class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.get<string[]>("roles", context.getHandler());
    if (!requiredRoles) return true; // no @Roles(...) declared — no restriction
    const req = context.switchToHttp().getRequest();
    return requiredRoles.some((role) => req.user.roles.includes(role));
  }
}

@UseGuards(AuthGuard, RolesGuard) // AuthGuard resolves identity FIRST, RolesGuard checks it SECOND
@Roles("admin")
@Delete("orders/:id")
deleteOrder(@Param("id") id: string) { /* only reached if req.user has the "admin" role */ }
```

Notice the two guards run in sequence, each with a single job: `AuthGuard` (part 1) resolves *who*; `RolesGuard` checks *what they're allowed to do*, and only after identity is already known. Collapsing both into one guard is possible for simple apps, but keeping them separate means a role check can be applied selectively (only some authenticated routes need it) without duplicating the identity-resolution logic.

## 2. Why Role Checks Aren't Enough: Object-Level Authorization

A role check answers "is this user an admin" — a coarse, resource-independent question. It does **not** answer "may this specific customer see this specific order," which requires comparing the request against the *actual resource* being accessed, not just the caller's role:

```ts
@UseGuards(AuthGuard)
@Get("orders/:id")
async getOrder(@Param("id") id: string, @CurrentUser() user: User) {
  const order = await orderService.findById(id);
  // A role check alone can't catch this — it needs the ACTUAL order's ownership,
  // which only exists once the resource itself has been fetched.
  if (order.customerId !== user.id && !user.roles.includes("admin")) {
    throw new ForbiddenException();
  }
  return order;
}
```

This is precisely the BOLA vulnerability covered from the API-design angle in `backend/api-design/10-api_security` — a guard running *before* the handler can check roles, but it generally cannot check resource ownership without first loading the resource, which is why this specific check commonly lives inside the handler (or a dedicated authorization service called from within it) rather than in a route-level guard.

## 3. Permission-Based Authorization: Finer-Grained Than Roles

Roles work well when access maps cleanly onto a small number of broad categories ("admin", "customer"). Real systems often need finer granularity — a specific *permission* rather than a whole role:

```ts
interface User {
  id: number;
  permissions: string[]; // e.g. ["orders:read", "orders:refund", "users:read"]
}

function requirePermission(permission: string) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user.permissions.includes(permission)) {
      return res.status(403).json({ error: `missing permission: ${permission}` });
    }
    next();
  };
}

app.post("/orders/:id/refund", requireAuth, requirePermission("orders:refund"), refundHandler);
```

Permissions decouple "what a user can do" from "what role they're labeled with" — a support agent role and a billing-admin role might both carry `orders:refund` without needing to share every other permission, which a pure role check can't express without either an explosion of role names or overly broad role definitions.

## 4. Where Guards/Middleware Hand Off to the Service Layer

Framework-level guards are well-suited to coarse, request-shape-only checks (is there a valid token, does this role/permission exist on the user) that don't require touching the database. Object-level checks that need the actual resource — is *this* order *this* user's — are business logic and belong in the service layer (`backend/frameworks/10-services_and_repositories`), not stuffed into a guard that would otherwise need to duplicate a repository lookup just to run an authorization check:

```
Guard/middleware  -> "does this token exist, is it valid, does this role/permission exist" (cheap, request-only)
Service layer      -> "does THIS user own THIS specific resource" (needs the actual data)
```

![An auth guard resolves identity (401 on failure), a roles guard checks role or permission (403), and the service checks ownership of the specific resource (404)](diagrams/01-authn-authz-layers.svg)

## Common Pitfalls

1. **Treating a passing role check as full authorization.** A role check is necessary but not sufficient the moment resource ownership matters — see section 2's BOLA connection.
2. **Duplicating a full resource lookup inside a guard just to check ownership.** This either duplicates the repository call the handler/service will make anyway, or forces the guard to depend on repositories it shouldn't need to know about — object-level checks belong past the guard, inside the handler or a dedicated authorization service.
3. **An explosion of narrowly-named roles instead of permissions.** `orders_read_only_no_refund_admin` as a role name is a sign the system actually needs permission-based checks, not more roles.
4. **Applying `RolesGuard` without `AuthGuard` running first.** A roles check against an unauthenticated (or not-yet-resolved) `req.user` either crashes or silently passes with undefined behavior — the two guards have a required order.

## Quick Reference

```
Role check         -> coarse, request-only: "is this user an admin" — a guard's job
Permission check    -> finer-grained than a role, same guard-level mechanism
Object-level check  -> "does THIS user own THIS resource" — needs real data, belongs in the service layer
Guard ordering      -> AuthGuard (who) MUST run before RolesGuard (what they're allowed)
```
