Why this matters
APIs must decide who can do what on which resource. As an API Engineer, you will design and enforce rules that prevent data leaks, cross-tenant access, and privilege abuse. RBAC (role-based access control) and ABAC (attribute-based access control) are the main tools to implement safe, auditable decisions.
- Protect multi-tenant data boundaries (e.g., users from one organization cannot read another’s records).
- Enforce least privilege with clear roles and precise policies.
- Enable flexible access decisions (e.g., time-limited, purpose-bound, or sensitivity-aware access).
Who this is for & Prerequisites
- Who this is for: API engineers, backend developers, and platform engineers responsible for securing endpoints and services.
- Prerequisites: Basic HTTP/REST knowledge, JWT or session basics, and familiarity with database schemas.
Concept explained simply
Authentication answers “Who are you?” Authorization answers “Are you allowed to do this?”
- RBAC: You get permissions via a role (e.g., editor can edit_post).
- ABAC: Decision uses attributes about the subject, resource, action, and context (e.g., owner == requester and request_time < link_expiry).
Mental model
Think: subject + action + resource + context → decision. By default, deny. Then allow only if policy says yes.
decision = allow if
subject_has_permission(action, resource) // RBAC
AND conditions_hold(subject, resource, context) // ABAC
else deny
Key principles
- Deny by default; allow by explicit policy.
- Least privilege: grant only the minimal permissions needed.
- Scope roles to tenants or projects to avoid cross-tenant access.
- Centralize policy definitions; enforce checks close to data access.
- Audit every decision path (who, what, when, why).
Worked examples
Example 1 — SaaS multi-tenant projects
Scenario: Multi-tenant project management API.
- RBAC roles (scoped per tenant):
org_admin,member,billing. - Permissions:
create_project,view_project,manage_members,view_invoices.
ABAC conditions:
- tenant_id in token must match project.tenant_id.
- Optionally, project.status != archived for edits.
// Pseudo policy
allow if subject.role in {org_admin, member}
and action in {create_project, view_project}
and subject.tenant_id == resource.tenant_id
Pitfall: If you only check role but not tenant_id, you risk cross-tenant leaks.
Example 2 — Healthcare record sensitivity
Scenario: Read EHR (Electronic Health Records).
- RBAC base role:
clinician. - ABAC:
subject.clearance >= resource.sensitivity,purpose_of_use in patient.consent.
// CEL-like
allow = subject.role == "clinician"
&& subject.clearance >= resource.sensitivity
&& purpose_of_use in resource.consent.allowed_purposes
Pitfall: Ignoring consent or purpose-of-use leads to policy non-compliance.
Example 3 — File download with time-limited access
Scenario: Download file owned by a user with optional pre-signed link.
- RBAC:
userrole hasdownload_own_files. - ABAC:
subject.user_id == resource.owner_idORlink.valid && now < link.expiry.
allow if (subject.role == "user" && subject.user_id == file.owner_id)
|| (context.link.valid && context.now <= context.link.expiry)
Pitfall: Forgetting expiry check makes links permanent.
Implementation steps
- Model permissions: Define actions (CRUD-by-resource, not endpoints). Keep names stable.
- Define roles: Group permissions per role, scoped to tenant/project where applicable.
- Identify attributes: Subject (tenant_id, clearance), resource (owner_id, sensitivity), context (ip, time, mfa).
- Write policies: Express allow-conditions with clear, testable logic; default deny.
- Enforce: Check before data access in handlers; propagate decision to downstream services.
- Cache carefully: Cache positive decisions briefly; invalidate on role/attribute change.
- Audit: Log decision inputs and outcomes (without leaking secrets).
Data model cheat-sheet
// RBAC tables (typical)
roles(id, name)
permissions(id, name)
role_permissions(role_id, permission_id)
user_role_assignments(user_id, role_id, tenant_id)
// Attributes stored/fetched
users(user_id, tenant_id, clearance, department)
resources(resource_id, tenant_id, owner_id, sensitivity, status)
Common mistakes & self-check
- Mistake: Checking role but not tenant or resource ownership. Fix: Always include tenant_id/owner checks.
- Mistake: Hardcoding endpoint-based permissions. Fix: Use action-by-resource (e.g., project.update).
- Mistake: Allow-by-default in unknown cases. Fix: Deny on error/timeouts/unknowns.
- Mistake: Stale decisions after role change. Fix: Short caches, revoke tokens on critical changes.
- Mistake: Inconsistent checks across services. Fix: Central policy or shared library and tests.
Self-check: Pick any sensitive endpoint and write the exact subject/resource/context attributes needed to reach an allow decision. If any attribute is missing at runtime, default to deny.
Exercises
Do these to cement the concepts. Compare with solutions, but try first.
Exercise 1 — Design an RBAC matrix for a blog API
Create roles and permissions for a blogging API with resources: posts, comments. Actions: create, read, update, delete, publish (posts only), moderate (comments only). Roles: admin, editor, author, reader. Add note on tenant/project scoping if you treat organizations.
- Deliverable: A list mapping roles → permissions (e.g., author → post.create, post.update_own, ...).
- Checklist:
- Least privilege for each role
- Separate publish from edit
- Ownership considered for authors
- Deny by default
Show hint
Start from actions; then assign to roles. Authors usually manage only their own posts. Editors can publish others’ posts. Readers: read-only.
Exercise 2 — Write an ABAC policy condition
Write a policy expression to allow UPDATE /users/{id} if the requester is the same user OR is a support agent with an open support ticket for that user.
- Deliverable: A single boolean expression using attributes: subject.user_id, subject.role, path.user_id, context.support_ticket_open.
- Checklist:
- Covers self-update
- Covers support with ticket open
- Deny by default otherwise
Show hint
Use OR across the two allowed cases; AND for their internal conditions.
Exercise 3 — Enforce tenant boundary in code
Write pseudocode for a handler that checks both permission and tenant boundary before returning a resource.
- Deliverable: 8–12 lines of pseudocode that reads a project, checks view_project, and ensures subject.tenant_id == project.tenant_id.
- Checklist:
- Deny by default
- Return 404 or 403 safely (avoid leaking existence across tenants)
- Audit log on deny
Show hint
Fetch minimal fields first; guard existence leaks by returning 404 when tenant mismatch.
Mini challenge
Design a combined RBAC+ABAC rule for archive_project: only org_admin may archive a project, and only if there are no active invoices and the project is not already archived. Write this as a short, testable condition.
Practical projects
- Build a policy middleware: Given a user token and request, evaluate allow/deny via a central function with unit tests.
- Migrate from endpoint-based allowlists to action-based permissions. Provide a mapping doc and refactor 3 endpoints.
- Add tenant-aware roles: Extend your schema to include tenant-scoped role assignments and update checks accordingly.
Learning path
- Review authentication basics (JWT claims, session management).
- Model permissions and roles per resource type; write unit tests for decision logic.
- Add ABAC attributes (owner, tenant, sensitivity, purpose) to decisions.
- Introduce a policy language (CEL-style or similar) for complex conditions.
- Implement auditing and short-lived caches with invalidation.
Next steps
- Pick one sensitive endpoint and add a robust RBAC+ABAC check today.
- Write 5 policy unit tests that cover allow, deny, and edge cases.
- Schedule a review to remove overly broad roles and split large permissions.
Authorization RBAC ABAC — Quick Test
This quick test is available to everyone. If you are logged in, your progress will be saved automatically.