Backend Engineering · Field guide 04
Identity, Authentication, and Authorization
Separate who a caller is, how that was proved, and what the caller may do now. This chapter connects protocol behavior to the decisions a production service must make.
01 / Foundation
One request crosses four security boundaries
A service cannot safely answer ‘may this request continue?’ with one vague authenticated flag. It needs separate evidence about identity, authentication, session state, and the requested resource.
Identity names a subject. Authentication establishes confidence that a claimant controls an authenticator bound to that subject. A session or token carries the result across requests. Authorization decides whether the resulting principal may perform one action on one resource under the current conditions. Each answer becomes an input to the next boundary, but none replaces it.
Consider a valid session for a member of tenant A requesting document 42. The session can identify the member without proving that document 42 belongs to tenant A, that the member owns it, or that the current assurance is high enough to delete it. The service must load trusted resource attributes and evaluate policy after credential validation, on every request.
- 01Identity
Name the subject with an issuer-scoped identifier.
- 02Authentication
Verify control of a bound authenticator.
- 03Session or token
Validate current state, scope, type, and time.
- 04Resource context
Load tenant, owner, classification, and action.
- 05Authorization
Return an explicit allow or deny decision.
Read diagram as text
The service identifies a subject, verifies authentication evidence, validates the current session or token, loads trusted attributes for the requested resource, and only then makes an explicit authorization decision.
02 / Foundation
Authenticators provide different kinds of assurance
An authentication ceremony proves control of an authenticator, not the claimant's honesty, device safety, or permission to access every resource.
Passwords are shared secrets that users can reveal to a convincing impostor and attackers can replay. OTP authenticators reduce dependence on one static secret, but manually entered OTPs remain phishable and replayable during their validity window. Public-key authenticators can bind proof to the verifier's origin, which is why phishing resistance is a property of the protocol rather than the presence of a second screen.
Choose assurance from risk. A low-impact read may accept a baseline session, while exporting sensitive data, deleting a resource, or changing membership may require a recent phishing-resistant step-up. The application should record the resulting assurance and its bounded lifetime rather than infer strength from a role name.
| Authenticator | Primary proof | Replay/phishing concern | Production implication |
|---|---|---|---|
| Password | Knowledge of a shared secret | Replayable and readily phished | Block breached/common values, rate-limit attempts, and never log the secret |
| One-time password | Possession plus a short-lived code | A live code can still be relayed or phished | Accept once, bound its lifetime, and do not label OTP alone phishing-resistant |
| Public-key authenticator | Possession of a private key and protocol proof | Can be verifier-name bound | Prefer for higher-assurance and phishing-resistant flows |
- Use generic authentication failures so account existence is not disclosed unnecessarily.
- Rate-limit failed attempts and monitor abuse without storing supplied credentials.
- Treat recovery and authenticator replacement as high-risk authentication events.
- Require a fresh step-up for sensitive actions rather than trusting an old login forever.
03 / Foundation
A session is a revocable state machine
Authentication is an event. A session is the changing server-side policy that decides whether its result can still be used.
A browser receives an opaque, high-entropy bearer value while the service stores only a lookup digest and session metadata. Authentication creates a fresh identifier instead of promoting an anonymous identifier supplied by the browser. Privilege changes rotate it again and invalidate the predecessor, closing the session-fixation path.
Idle and absolute deadlines answer different questions. The idle deadline limits unattended use; the absolute deadline bounds the total lifetime even when requests continue. Logout, compromise response, recovery, and administrative action can revoke earlier. Every lookup must reject expired, revoked, and replaced records before refreshing activity.
- S0Anonymous
No authenticated session exists; browser input has no privileged meaning.
- S1Active
Authentication issues a fresh browser value and stores its lookup digest.
- S2Elevated
Step-up raises assurance for a bounded period and rotates the identifier.
- S3Expired
Idle or absolute time reaches its limit; use fails closed.
- S4Replaced
A rotated predecessor remains unusable even if copied earlier.
- S5Revoked
Logout, recovery, or compromise response terminates acceptance.
Read diagram as text
A user begins anonymous, receives a fresh active session after authentication, may temporarily step up to elevated assurance, and eventually reaches an unusable expired, replaced, or revoked state. Rotation never makes the predecessor active again.
export function inspectSession(
record: SessionRecord,
now: number,
idleTimeoutMs: number,
): SessionInspection {
if (record.revokedAt !== null) {
return { ok: false, reason: "revoked" };
}
if (record.replacedByDigest !== null) {
return { ok: false, reason: "replaced" };
}
if (now >= record.expiresAt) {
return { ok: false, reason: "absolute-expired" };
}
if (now - record.lastSeenAt >= idleTimeoutMs) {
return { ok: false, reason: "idle-expired" };
}
return { ok: true };
}05 / Foundation
A JWT is a claims container, not session magic
Base64url decoding reveals attacker-controlled fields. Trust begins only after cryptographic verification and the complete application validation profile succeed.
A maintained security library should parse the compact token, reject algorithms outside an explicit allowlist, verify the signature with key material bound to the configured issuer, and enforce structural limits. Application policy must then validate issuer, audience, token type, subject, expiry, not-before time, and mutually exclusive rules for different token kinds. A token for another API or an ID token used as an access token must fail even when its signature is valid.
The example type is intentionally named VerifiedTokenEnvelope: it represents the boundary after library verification, not an object produced by decoding. The helper keeps only allowlisted roles and converts claims into a minimal principal. Resource authorization still occurs afterward because neither a valid signature nor an admin-looking claim proves access to a particular tenant object.
export function validateVerifiedToken(
envelope: VerifiedTokenEnvelope,
policy: TokenPolicy,
nowSeconds: number,
): TokenValidation {
const { claims } = envelope;
if (!policy.algorithms.includes(envelope.algorithm)) {
return { ok: false, reason: "unexpected-algorithm" };
}
if (claims.iss !== policy.issuer) {
return { ok: false, reason: "wrong-issuer" };
}
if (!Array.isArray(claims.aud) ||
!claims.aud.includes(policy.audience)) {
return { ok: false, reason: "wrong-audience" };
}
if (claims.typ !== policy.type) {
return { ok: false, reason: "wrong-token-type" };
}
return validateTimesAndBuildPrincipal(claims, policy, nowSeconds);
}- Configure accepted algorithms; never select trust from an unverified header alone.
- Bind verification keys to the expected issuer and validate the intended audience.
- Give access, identity, logout, and security-event tokens mutually exclusive validation profiles.
- Reject missing or invalid time and subject claims required by your profile.
- Never put secrets in a JWT payload; signing does not encrypt its claims.
07 / Foundation
Security state must be able to move backward
Systems that can create trust but cannot quickly reduce it turn one stolen credential or mistaken grant into a long-lived incident.
Revocation is a propagation problem. A server-side session can consult current state on each request, while a self-contained access token may remain accepted until expiry unless the service adds an online status or version check. Short token lifetimes reduce the window but do not replace refresh-token rotation, reuse detection, or incident procedures where those risks apply.
Recovery, password change, authenticator replacement, membership removal, and role reduction should identify which sessions and tokens become stale. Audit events need stable subject, tenant, action, resource, decision, policy version, and correlation identifiers—but never passwords, OTPs, session bearer values, complete tokens, or sensitive resource bodies.
| Event | State transition | Operational evidence |
|---|---|---|
| Logout | Revoke the active session | Session identifier category, subject, time, outcome |
| Privilege change | Rotate session and recompute authorization | Old/new policy version and administrative actor |
| Account recovery | Revoke affected sessions and require renewed assurance | Recovery method, notifications, revocation completion |
| Refresh-token replay | Revoke the token family and investigate | Family identifier, reuse signal, affected client—never raw token |
| Tenant membership removal | Deny future resource decisions immediately | Membership version and enforcement timestamp |
- Define which events revoke one session, every session, or a token family.
- Test propagation delay instead of assuming a revocation write is instantly visible everywhere.
- Version authorization policy so an audit decision can be reconstructed.
- Keep credential material and sensitive resource content out of logs and traces.
08 / Foundation
Debug the boundary that made the decision
An unexpected 401, 403, or allow result becomes tractable when authentication evidence, session state, resource context, and policy output are inspected separately.
Start with the request correlation identifier and the credential transport mechanism, not the raw credential. Confirm whether authentication failed, the session or token was expired or revoked, the issuer/audience/type profile matched, and a minimal principal was created. Then inspect the trusted resource tenant, owner, classification, requested action, assurance, and policy version used by authorization.
Keep public failures deliberately small. A service may map missing and unauthorized resources to the same response to avoid confirming existence, while internal audit records a safe decision category. Reproduce with deterministic policy fixtures and a controlled clock; do not paste production tokens into logs, tickets, tests, or decoding websites.
- Distinguish missing credentials, invalid credentials, expired state, and insufficient permission internally.
- Confirm the verification key belongs to the expected issuer and the audience names this service.
- Load the resource independently and compare its tenant and owner with the principal.
- Check idle, absolute, elevated-assurance, and revocation times with one documented clock policy.
- Re-run the exact authorization matrix row using sanitized identifiers and the deployed policy version.
Design questions
- Which decisions in your current service are hidden inside a single authenticated boolean?
- What event rotates a session identifier, and can the predecessor still be accepted anywhere?
- Can a valid token issued for another audience or token type reach your handlers?
- Which test proves that an administrator from one tenant cannot read a guessed resource ID from another tenant?