Security
Part 6 of 6 · AuthorizationAuthZ Enforcement — Gateway, Service & Data Filters
Enforce at gateway + service + data filters; fail closed; defense in depth for AuthZ.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Three layers, or a confident edge
Prefer
Gateway, service, and data filter
The edge rejects the anonymous and the obviously wrong role. The service checks this resource. The query cannot return another tenant even if the service is wrong.
- Internal calls and workers hit a PEP too.
- Row security survives a missing predicate in one handler.
- Fail closed is the same answer at each layer when the PDP is unsure.
Alternative
AuthZ only on the public API
The dashboard is safe. The worker uses a god credential. A SQL console and a forgotten admin route return every tenant.
- Postmortems often start with that sentence.
- A 24-hour allow cache resurrects a deleted grant.
- Break-glass becomes a comment in the middleware.
One refund, three checks
Each layer knows less than the one inside it, and still refuses to be the only one.
- 1
Gateway
Identity is present. A coarse role or scope includes orders. Geo or IP policy can live here. This is not the resource check. - 2
Service PEP
orders:refund on order 123, via the PDP you chose in the previous lesson. Load the resource, then decide. Do not trust a flag the client sent. - 3
Data filter
The query is constrained to the viewer's tenant, by a predicate or by row security. A bug in the handler still cannot fetch the other tenant. - 4
Uncertainty
Timeout, missing attribute, or cache keyed wrong: deny. Break-glass is a separate, expiring, audited path.
Overview
A correct policy enforced in the wrong place still fails. Put PEPs at the API gateway, in service middleware, and in the data layer so bypass paths stay closed. Add fail-closed defaults, short decision caches, and audited break-glass.
Breach write-ups often say authorization existed on the public API while workers, admin scripts, or raw SQL ignored it. Interviews probe that gap. On-call probes the cache TTL that brings a deleted grant back.
This lesson assumes identity already exists. It only enforces AuthZ.
Enforcement map
Flow
- 1
1. Client request
- next2. Gateway coarse PEP
- 2
2. Gateway coarse PEP
- next3. Service action PEP
- 3
3. Service action PEP
- next4. PDP allow or deny
- 4
4. PDP allow or deny
- next5. 403 if deny
- next6. Query with row filters
- 5
5. 403 if deny
- 6
6. Query with row filters
- next7. Return authorized rows
- 7
7. Return authorized rows
Lesson map
AuthZ Enforcement — Gateway, Service & Data Filters
Enforce at gateway + service + data filters; fail closed; defense in depth for AuthZ.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB a["1. Client request"] b["2. Gateway coarse PEP"] c["3. Service action PEP"] d["4. PDP allow or deny"] a -->|1. Client request| b b -->|2. Gateway coarse PEP| c c -->|3. Service action PEP| d
| Layer | Pros | Cons | Best for |
|---|---|---|---|
| API gateway | Central, early, uniform logs | Coarse; internal calls skip it | Identity present, role allowlists, geo |
| Service middleware | Exact action and resource | Must exist in every service | Primary fine-grained AuthZ |
| Data filters / row security | Survives buggy app code | Easy to misconfigure; query plans change | Tenant, owner, confidentiality |
| Worker PEP | Covers async paths | Often forgotten | Jobs that call the same PDP |
Rule of thumb: the gateway is a seatbelt, the service PEP is the brake, row security is the airbag. Sensitive data wears all three.
Story: the gateway checks that the caller is authenticated and that a coarse orders capability is present. The service asks the PDP whether this subject may refund order 123. The SQL includes the viewer's tenant, or a row security policy does, so a missing WHERE in one handler cannot return another tenant's rows.
Fail closed, cache, break-glass
Fail closed. PDP timeout, parse error, or missing attributes become deny, or a safe read-only subset you tested. "Allow this once because the PDP timed out" is how a partial outage becomes a breach.
Cache keys include subject, action, resource version, and policy version. Invalidate on revoke, role change, and tuple delete. A short TTL plus an explicit purge beats a day-long allow. Do not share a cache entry across tenants. Caching denies can create an existence oracle; decide that on purpose.
Break-glass is a pre-provisioned emergency role or policy overlay: dual control, expiry, immutable audit, and a retrospective. It is not a chat message asking someone to comment out the check.
404 vs 403. Pick a deliberate policy. Hiding existence and leaking existence are both products. Write it down, test the swap-id case, and do not let each handler invent a status.
Sandbox: three layers, then a versioned cache
Alice in tenant t1 can read order 1. Order 2 is tenant t2. The data filter removes it before the service PEP runs, so the caller gets a deny rather than Bob's total. The gateway still ran.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Expect an allow, then key moved with policy, then deny when the PDP throws. The old cache entry is not consulted, because the version is part of the key and bumpPolicy clears the map.
Interview Q&A
Why is gateway AuthZ insufficient alone?
Answer
Internal calls, jobs, and direct datastore access skip the edge. Enforce at each trust boundary. The gateway is the coarse PEP, not the whole design.
What belongs at the gateway vs the service?
Answer
Gateway: authentication already done, coarse roles or scopes, IP or geo, rate limits. Service: this action on this resource with full context. Data layer: tenant and row isolation. Scopes themselves are explained on the OAuth and OIDC pages; here they are only a coarse gate.
How do you cache AuthZ safely?
Answer
Put policy version and resource version in the key with subject and action. Keep the TTL short. Purge on revoke. Never reuse an entry across tenants. Treat cached denies as a possible existence oracle and choose that behavior on purpose.
What is fail-closed?
Answer
On uncertainty (timeout, error, incomplete context) choose deny or a tested safe subset. Do not choose allow because the dependency is unhappy.
How should break-glass work?
Answer
A pre-provisioned path with dual control, expiry, immutable audit, and a mandatory review. Disabling middleware by hand in production is not break-glass.
What is row security good for?
Answer
A predicate the database enforces, such as tenant id matching a session setting, so an application bug cannot leak another tenant's rows. It is not a substitute for the service PEP. Admin connections that bypass it are an incident waiting.
How do you prevent IDOR when middleware exists?
Answer
Authorize the resource id from the request after you load it, or call a check API. Do not trust a client-supplied owner flag. Pair that with the data filter. Add a test that swaps the id.
404 or 403?
Answer
Either can be correct. 403 admits the id exists. 404 hides it and can confuse legitimate clients. Pick one policy per resource type, document it, and test it. Do not mix them by accident across handlers.
What about workers?
Answer
A queue consumer with a god credential and no PEP is a bypass. The job should call the same PDP, with the acting subject or a tightly scoped service identity, and the query should still carry the tenant filter.
Pitfalls
Draw gateway, service, and database for "read order by id." Mark what each layer knows. Then add a worker that refunds the same order. Say where it calls the PDP, what the cache key contains, and what you return if the PDP times out.