Phase 4 · Authentication & SecurityModule 28~64 min read

Authorization, RBAC & Multi-Tenancy

Enforce object ownership, roles, permissions, and tenant isolation at every query and service boundary.

What you'll learn

Authentication identifies a caller; authorization decides whether that caller may perform this action on this object in this tenant. Put that decision in services and tenant-scoped repositories.

By the end of this lesson, you'll be able to:

  • Separate identity from permission
  • Enforce ownership and roles
  • Scope every data query by tenant
  • Test horizontal and vertical escalation

Core mental model

Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.

ConceptWhat it meansDecision rule
SubjectThe authenticated principal and trusted claimsDerive it server-side, never from body ownerId
PolicyA pure decision over subject, action, and resourceCentralize rules that must stay consistent
Tenant scopeThe partition boundary for customer dataInclude tenant identity in every read and write predicate

Professional workflow

Build and verify Node.js programs from the terminal in small, observable steps.

  1. Define the authorization policy boundary: inputs, outputs, invariants, ownership, and expected failures.
  2. Design the data or message contract before choosing implementation details.
  3. Implement the smallest correct path with dependencies passed explicitly.
  4. Add validation, failure translation, cleanup, and concurrency behavior.
  5. Verify the boundary with realistic data and at least one adversarial case.
  6. Measure or observe the behavior before optimizing or extracting abstractions.

Keep the feedback loop short

Run the smallest useful command after every meaningful change. Read the complete error message before editing again, and keep inputs and outputs visible while you learn.

Guided code lab

Authorize through the query boundary

The caller cannot supply tenant ownership; the repository query both locates and scopes the object.

complete-task.js
export async function completeTask(subject, taskId) {
  const task = await tasks.findByIdForTenant(taskId, subject.tenantId);
  if (!task) throw new NotFoundError('Task');
  if (!can(subject, 'task:complete', task)) throw new ForbiddenError();
  return tasks.complete(task.id, subject.tenantId);
}

Production practice

Contract

Every use case receives a trusted subject and enforces action, object, and tenant scope before side effects.

Verification

Test same-role cross-tenant access, other-owner objects, member/admin boundaries, revoked membership, bulk operations, and indirect identifiers.

Operations

Audit sensitive allow and deny decisions without secrets, review role changes, and make emergency privilege revocation effective quickly.

Common failure mode

Checking a role in middleware but querying by object ID alone still permits cross-tenant data access.

Independent workshop

Add tenant membership, roles, and object ownership to the API.

Your finished workshop must include:

  • Trusted subject model
  • Policy functions
  • Tenant-scoped repositories
  • Role matrix
  • Cross-tenant tests
  • Audit events

Definition of done

Run the happy path and at least two edge cases, keep responsibilities separated, and add a short README explaining how to run the program.

Recap & quick check

Key takeaways

  • Identity is not permission
  • Authorization is contextual
  • Queries enforce tenant scope
  • Deny by default
  • Negative tests are essential

Quick check

1. Where should tenantId come from?

2. What is BOLA?

3. What should policy default to?

Next: API Hardening: Validation, Headers, CORS & CSRF