In brief: A valid token proves an identity claim; it does not prove that every requested object or operation is allowed.
The problem
Long-lived API keys leak, JWT validation is misconfigured, object IDs cross tenant boundaries, and webhook endpoints trust any payload that reaches them. Authentication alone leaves the most important authorization decisions unresolved.
Why the simple answer is not enough
JWT libraries verify signatures only when configured correctly. The service must also reject unexpected issuers, audiences, algorithms and stale keys.
Horizontal object authorization is a common failure: a user may read one invoice but changes an ID to reach another. The query itself should carry the authorised scope.
System flow
size and abuse limits→Identity
issuer and audience→Policy
tenant and object access→Audit
decision and key lifecycle
Architecture decisions
Centralise policy, localise enforcement
Share policy definitions and tests, but enforce at every service that owns data. A gateway cannot authorise fields it does not understand.
Prefer workload identity
Use short-lived machine credentials issued to a workload rather than copying permanent secrets into environments.
Treat webhooks as untrusted input
Verify signatures before parsing, store delivery IDs, enforce timestamp windows and process side effects idempotently.
Going deeper
Authorisation must be data-aware
Role names alone are too coarse. Decisions often need tenant membership, resource ownership, record state and field sensitivity.
Break-glass is a product feature
Emergency access needs approval, expiry, reason, narrow scope and immutable audit. An undocumented admin bypass becomes the weakest policy path.
Implementation path
- Validate issuer, audience, algorithm, expiry and key status for every token.
- Authorise action plus tenant plus object at the server boundary.
- Sign webhooks over raw bytes with timestamp, replay protection and rotating secrets.
Technical example: Webhook verification envelope
signed = timestamp + '.' + rawBody
expected = HMAC(activeSecret, signed)
reject unless constantTimeEqual(signature, expected)
reject if abs(now - timestamp) > 5 minutes
reject if deliveryId already processedDuring rotation, accept the current and next secret for a short overlap, identify which key verified, and never log the secret or raw sensitive payload.
Failure modes to design for
- A gateway validates a token but an internal service trusts spoofed identity headers.
- Clock skew rejects every webhook after a node time fault.
- Revoked credentials remain accepted in a long cache.
What to observe
| Signal | Why it matters |
|---|---|
| Denied decisions by policy reason | Finds attacks, broken clients and accidental policy regressions. |
| Credential age and rotation status | Exposes long-lived or orphaned access. |
| Webhook signature and replay failures | Separates integration mistakes from active abuse. |
How to validate the design
- Run a matrix of users, tenants, objects and actions including negative cases.
- Rotate and revoke keys while traffic continues.
- Replay and modify signed webhook payloads, including duplicate delivery IDs.
Safe rollout plan
Inventory credentials and authorisation checks first. Add decision logging without changing outcomes, then migrate one API family to shared policy tests. Shorten credential lifetime gradually and rehearse emergency rotation before enforcing it everywhere.
Production checklist
- Service credentials are short lived and scoped to one workload.
- Key rotation supports overlap and immediate revocation.
- Denied access and privileged changes produce tamper-resistant audit events.
Takeaway
API security is continuous authority reduction, not a login gate.
