Authentication vs Authorization

Who you are vs what you can do. The two-word vocabulary every API designer must use precisely.

Concept Foundational
11 min read
authn authz security identity access-control

Summary#

Authentication (AuthN) answers: who are you? It binds the caller to an identity — a user account, a service principal, a device. The mechanisms are passwords, certificates, hardware tokens, single-sign-on assertions, biometrics, and combinations of these (multi-factor authentication).

Authorization (AuthZ) answers: what are you allowed to do? Given an established identity, AuthZ decides whether this caller can perform this operation on this resource. The mechanisms are roles, scopes, attributes, relationships, and policies.

These are two different questions, separated by a clean conceptual line: identity is established once per session; authorization is checked on every request. The professional vocabulary uses both words, never substitutes one for the other, and never describes a system as “doing auth” without saying which half.

The most common API-security failure of the last decade — broken object-level authorization (BOLA) — is the failure to do AuthZ correctly even when AuthN is solid. The second most common — treating an OAuth 2 access token as proof of identity — is the failure to keep AuthN and AuthZ distinct in the design. Both are vocabulary failures before they are code failures.

Why it matters#

Three reasons the AuthN/AuthZ distinction is load-bearing:

  • They fail differently and require different fixes. AuthN failures look like “someone got in pretending to be Alice.” AuthZ failures look like “Alice got at Bob’s data.” The root causes are different; the audit trails are different; the regulatory implications are different (a GDPR breach is usually an AuthZ failure). A team that lumps them together cannot diagnose the failure they are about to ship.
  • They live in different parts of the request lifecycle. AuthN runs once per session or per token — validate a JWT, check a session cookie, complete an mTLS handshake. AuthZ runs on every operation, often multiple times per request (gateway-level coarse AuthZ, then application-level fine-grained ownership checks). Confusing the layers produces both holes (“we authorised at the gateway, we don’t need to check again”) and waste (“we re-validate the JWT signature on every database query”).
  • The OAuth 2 vs OpenID Connect distinction depends on it. OAuth 2 is an authorization protocol — it issues access tokens that say “the bearer of this token can do X.” It does not say who the bearer is. OpenID Connect layers identity on top of OAuth 2 via an id_token. “Sign in with Google” is OpenID Connect; “Connect your Google Calendar to my app” is OAuth 2. Mixing these is the most common API-security bug in the protocol family.

In an interview, the candidate who says “we’d use OAuth for login” has just failed the vocabulary check. The senior version: “we’d use OpenID Connect — that’s OAuth 2 plus an identity layer — and the API would verify the id_token for AuthN, then check role and ownership for AuthZ.”

How it works#

Authentication — establishing identity#

AuthN binds an HTTP request to an identity. The mechanism varies by context:

  • Username + password — the historical default. Hashed and salted server-side (bcrypt / argon2 / scrypt; never plain SHA-256). Always sent over TLS. Almost always paired with MFA in 2026.
  • Session cookies — the post-login state. The server stores a session record; the cookie carries an opaque session ID; every subsequent request re-fetches the session. The cookie has HttpOnly, Secure, SameSite=Lax flags.
  • Bearer tokens (JWT or opaque) — short-lived strings sent in Authorization: Bearer <token>. JWTs carry claims (sub, iss, exp, scopes) inside the token, signed by the issuer; opaque tokens are looked up server-side. Issued by an OAuth 2 / OpenID Connect provider.
  • API keys — long-lived static strings, identifying the calling application, not a user. Used for server-to-server and machine-to-machine calls.
  • Mutual TLS — the client presents a certificate during the TLS handshake; the server’s TLS layer verifies the chain and exposes the client’s identity to the application. Dominant pattern in zero-trust service meshes.
  • Single sign-on (SSO) — SAML for enterprises, OpenID Connect for everything else. The user authenticates once at an identity provider; downstream apps trust signed assertions from that provider.
  • Multi-factor authentication (MFA) — TOTP, push notifications, hardware tokens (WebAuthn / passkeys). Adds a factor that AuthN-via-password alone cannot establish.

A well-designed API typically supports two AuthN paths: one for users (OpenID Connect / session) and one for machines (API keys or mTLS). Mixing the two paths into one entrypoint is a code smell.

Authorization — deciding what’s allowed#

AuthZ runs after AuthN. Given that we know who is calling, the question is what they can do. Four families dominate:

Role-Based Access Control (RBAC)#

Users have roles; roles have permissions; permissions are checked on operations.

User Alice
└─ role: admin
└─ permissions: [orders.read, orders.write, users.read, users.write]
Operation: DELETE /orders/123
required permission: orders.write
→ Alice has orders.write → ALLOW

Cheap, well-understood, ships in every web framework. Limitations: roles get bloated (the “kitchen sink admin”), roles do not naturally express ownership (“Alice can edit her own orders but not Bob’s”), and inheriting roles across organisations gets messy.

Attribute-Based Access Control (ABAC)#

Decisions are functions of attributes — user attributes (department, clearance, tenure), resource attributes (sensitivity, owner, classification), environmental attributes (time of day, source IP).

ALLOW if:
subject.department == resource.department
AND subject.clearance >= resource.sensitivity
AND environment.time within business_hours
AND environment.source_ip in allowed_ranges

Expressive enough for any policy you can write; hard to audit at scale; tooling (Open Policy Agent, AWS IAM policies) makes it tractable.

Relationship-Based Access Control (ReBAC)#

Permissions follow relationships between objects. Pioneered by Google’s Zanzibar; the model behind Google Drive sharing, GitHub repo permissions, and Figma project membership.

doc:report-q3#viewer@group:finance#member
group:finance#member@user:alice
→ Can Alice view doc:report-q3?
→ Walk the graph: user:alice → group:finance member → doc:report-q3 viewer → YES

ReBAC is the right model whenever permissions are inherently a graph — shared documents, hierarchical folders, organisational hierarchies. Production implementations: SpiceDB, OpenFGA, Permify.

Scopes (OAuth 2-style)#

Scopes are coarse-grained permission strings carried in an access token: calendar.read, repo.write, admin:org. They tell the resource server “this token is allowed to do these things.” Scopes alone are not enough — they say what surface a caller can hit, not which resources on that surface they own. Scopes need to combine with ownership checks to make BOLA defence work.

Token has scope: calendar.read
Request: GET /calendar/events?owner=alice
AuthZ check 1: token has calendar.read scope? → yes
AuthZ check 2: caller (= sub claim) == alice? → must check

Where they meet — and where they get confused#

A complete authenticated and authorized request:

1. AuthN — who are you?
Header: Authorization: Bearer eyJ...
Server: verify JWT signature → caller = user_a3f9c2 (sub claim)
2. Coarse AuthZ — does this token have any access to this surface?
Token scope: calendar.read
Endpoint: GET /calendar/events (requires calendar.read scope)
→ allow into the application
3. Fine AuthZ — does this caller own this resource?
Requested resource: calendar event evt_42
Database: owner of evt_42 is user_a3f9c2
→ allow
4. Optional — secondary checks
Re-authentication for high-stakes operations (admin actions)
MFA challenge for sensitive resources

The structural failure that produces BOLA is skipping step 3. The token was authenticated; the scope was approved; the application then assumed “you’ve gotten this far, you can read whatever you ask for” and read out the resource without checking ownership. Two examples of the failure mode:

  • The implicit-owner anti-pattern. GET /orders/{id} looks up the order by id alone and returns it. The caller’s identity from step 1 is never compared to the order’s customer_id. Anyone with a valid token can iterate id values and exfiltrate all orders.
  • The trust-the-client anti-pattern. GET /orders?customer_id=alice filters by customer_id from the query parameter, trusting that the client only ever sends its own ID. The fix: ignore the parameter; use the authenticated caller’s ID from the token.

Worked example — getting it wrong#

Two real-shaped bugs:

Bug 1: Access token as identity.

A team builds a “Sign in with Acme” feature using OAuth 2. After the OAuth flow, the third-party app holds an access token. The team writes:

# Hand the token to our /me endpoint to identify the user
resp = acme.get("/v1/me", headers={"Authorization": f"Bearer {access_token}"})
user_id = resp.json()["id"]
session["user_id"] = user_id # log them in

The bug: an access token is an authorization artefact, not an authentication artefact. The third-party app received the token via the OAuth flow but cannot prove the token was issued for them. An attacker who steals an access token from any other Acme-integrated app can present it here and impersonate that user.

The fix: use OpenID Connect. The id_token is a signed JWT bound to the client_id of the receiving app; it can be verified locally and proves identity. Or, if sticking with OAuth 2, validate the access token at Acme’s introspection endpoint and check the aud (audience) claim matches the receiving app’s client_id.

Bug 2: Session cookie authorising admin action without re-auth.

A user logged in 6 hours ago. The session cookie is still valid. They click “Delete Organisation.” The endpoint authenticates the cookie, finds the user is an admin role on this org, and runs the deletion.

The bug: an admin role grants the capability but a 6-hour-old session is weak evidence the human at the keyboard is the admin. Stolen laptops, shared computers, social-engineering scenarios all match this profile.

The fix: high-stakes operations require step-up authentication. The endpoint checks the session’s auth_time claim; if it’s older than 5 minutes, redirect to re-auth (password + MFA again) before executing. GitHub’s “sudo mode,” AWS’s MFA-required-for-deletion, Google Workspace’s re-auth-for-billing all implement this pattern.

Variants and trade-offs#

The AuthN/AuthZ split shows up at every scale, but the mechanisms shift:

User-facing API. AuthN via OpenID Connect or sessions; multiple factors expected. AuthZ via roles + ownership checks; scopes if third parties are involved. Step-up auth for sensitive actions. Audit log on every write.

Service-to-service API. AuthN via mTLS or signed service tokens (SPIFFE / JWT-SVID). AuthZ via service identity ACLs (“payments-service can call ledger-service”) plus per-call resource checks. No interactive step-up — the calling service either has authority or it doesn’t.

DimensionUser-facingMachine-to-machinePublic partner
AuthN mechanismOIDC / session + MFAmTLS / signed service tokenOAuth 2 client credentials
AuthZ mechanismRBAC + ownershipService ACL + resource checkScopes + ownership
Token lifetimeAccess 1h, refresh 30dmTLS cert 1hAccess 1h, refresh 90d
Step-up authFor admin actionsN/AN/A
Identity proofsub claim in id_tokenCert CN or SPIFFE IDclient_id
BOLA defencePer-resource ownership checkPer-resource ACL checkPer-resource ownership check

When this is asked in interviews#

The AuthN/AuthZ distinction is a single-question test of vocabulary discipline. Watch for these moments:

  • “How would you handle authentication?” Answer for AuthN specifically — identity mechanism, token format, MFA. Then proactively segue: “And for authorization, separately, we’d…”
  • “How would users sign in with Google?” OpenID Connect, not OAuth 2. Get the protocol right; the interviewer is checking.
  • “How do you prevent a user from accessing another user’s data?” Object-level authorization (BOLA defence). Every read endpoint that takes a resource ID enforces resource.owner_id == authenticated_caller_id. Mention the failure mode by name.
  • “What about admin actions?” Step-up authentication. Re-authenticate, re-MFA, even if the session is otherwise valid. Audit log everything.
  • “How do you do machine-to-machine authentication?” mTLS or OAuth 2 client credentials. Avoid baking long-lived API keys into client code if you can issue short-lived service tokens instead.

The senior-signal phrasing: “Authentication tells me who the caller is; authorization tells me what they can do; I keep them separate in the design and the code.” Said once, with the right vocabulary, it covers half the security round.

Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.