--- name: implementing-authorization description: Implements permission decisions in backend services — RBAC/ABAC models, resource-ownership checks, IDOR prevention, multi-tenant isolation, and centralized policy enforcement. Use when the user asks to add roles, permissions, access control, admin-only routes, ownership checks, tenant isolation, or asks why a user can see another user's data. Do not use for identity verification and login flows (implementing-authentication) or OS and file-system permissions. --- # Implementing Authorization ## When to use / when NOT to use - **Use for:** deciding what an authenticated caller may do — roles, permissions, ownership checks, tenant isolation, policy middleware, privileged-action auditing. - **Do NOT use for:** logging users in (implementing-authentication), database GRANTs (db-skills/securing-databases), or OS/file permissions. ## Core rules 1. **Authorize on every request, server-side.** Client-side hiding of buttons is UX, not security. 2. **Deny by default.** Routes without an explicit policy are rejected, not allowed. - ✅ `@require(perm="reports:read")` on each route; unannotated routes 403 in middleware - ❌ "Only add checks to the sensitive endpoints" 3. **RBAC is the default model** (user → roles → permissions). Reach for ABAC (attribute rules) only when decisions depend on resource attributes (owner, status, amount thresholds). 4. **Authentication is not authorization — check ownership.** Fetching by ID must scope to the caller. - ✅ `SELECT ... WHERE id = :id AND owner_id = :caller` - ❌ `SELECT ... WHERE id = :id` after login — classic IDOR 5. **Multi-tenant: scope every query by `tenant_id`,** derived from the session/token — never from the request body or URL. Use Postgres row-level security as a backstop where available. 6. **Centralize policy in one module/middleware.** Scattered `if user.role == "admin"` checks drift and rot; route handlers call `authorize(caller, action, resource)` and nothing else. 7. **Audit privileged actions** (role grants, data exports, deletions): who, what, when, from where — to an append-only log. ## Workflow 1. Enumerate actions and resources; write the permission matrix (roles × actions) before code. 2. Implement the central `authorize()` + deny-by-default middleware (rule 2, 6). 3. Add ownership/tenant scoping at the data layer (rules 4–5) so a missed route check cannot leak cross-tenant data. 4. Wire the audit log for privileged actions (rule 7). 5. **Validate:** as user A, request user B's resource by ID (expect 403/404); as a role without the permission, call each privileged route (expect 403); confirm an unannotated test route is rejected by default. All three must pass before shipping. ## Edge cases & failure modes - **403 vs 404:** return 404 for resources the caller must not know exist (cross-tenant); 403 within a tenant where existence is not secret. Pick per resource and stay consistent. - **Role changes mid-session** → re-read roles per request (or short cache ≤60 s); revoke sessions on demotion. - **Background jobs and internal services** → they get their own scoped identities, never a shared "system = superuser" that skips `authorize()`. - **Batch endpoints** → authorize each item, not just the endpoint; report per-item denials. ## References Copy-paste patterns and gotchas: see [references/patterns.md](references/patterns.md).