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.
| Concept | What it means | Decision rule |
|---|---|---|
| Subject | The authenticated principal and trusted claims | Derive it server-side, never from body ownerId |
| Policy | A pure decision over subject, action, and resource | Centralize rules that must stay consistent |
| Tenant scope | The partition boundary for customer data | Include tenant identity in every read and write predicate |
Professional workflow
Build and verify Node.js programs from the terminal in small, observable steps.
- Define the authorization policy boundary: inputs, outputs, invariants, ownership, and expected failures.
- Design the data or message contract before choosing implementation details.
- Implement the smallest correct path with dependencies passed explicitly.
- Add validation, failure translation, cleanup, and concurrency behavior.
- Verify the boundary with realistic data and at least one adversarial case.
- Measure or observe the behavior before optimizing or extracting abstractions.
Keep the feedback loop short
Guided code lab
Authorize through the query boundary
The caller cannot supply tenant ownership; the repository query both locates and scopes the object.
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
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
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