TL;DR
- Authentication (authN): prove who the user or client is (passwords, MFA, WebAuthn, SSO/OIDC). Produces a session or token that asserts identity.
- Authorization (authZ): decide what the authenticated principal may do (roles/scopes/permissions/policies). Evaluated per request/action.
- OAuth = delegated authorization (access to APIs). OIDC = OAuth + an identity layer (ID Token) for login.
- For web apps: prefer HTTP‑only secure cookies for sessions; for APIs: Bearer tokens (JWT or opaque) with audience & scope checks.
- Separate identity from policy. Enforce authZ at the edge (gateway/middleware) and in service (defense in depth).
The mental model (in one minute)
User → (Authentication) → Identity established (session / tokens)
↓
(Authorization) → Is this principal allowed to DO X on RESOURCE Y?
- AuthN answers: “Are you really Alice?” (and keeps you logged in).
- AuthZ answers: “Given you are Alice, can you
DELETE /posts/42?”
Authentication (prove identity)
Factors & methods
- Something you know (password, passphrase) — protect with hashing (bcrypt/argon2) & rate limits.
- Something you have (TOTP app, security key, device) — MFA.
- Something you are (biometrics) — usually as a device unlock factor.
- WebAuthn / Passkeys: phishing‑resistant public‑key login.
- SSO with OIDC/SAML: delegate login to an Identity Provider (IdP).
Sessions & tokens
- Cookie session (server‑side or signed): best for browser apps; set
HttpOnly; Secure; SameSite=Lax/None. Protect from CSRF (SameSite + anti‑CSRF tokens) and XSS (no JS access). - Bearer tokens for APIs:
- Access Token (often JWT) → presented to API.
- ID Token (OIDC JWT) → for the client app to learn who logged in.
- Validate issuer (
iss), audience (aud), expiry (exp), signature (JWKS). - Prefer Authorization Code + PKCE for web/mobile; avoid deprecated implicit flow.
Minimal login flow (OIDC)
- App redirects to IdP (Auth Code + PKCE).
- User authenticates; IdP redirects back with code.
- App exchanges code for Access Token (+ ID Token).
- App stores session (cookie) or token; calls APIs with
Authorization: Bearer <access_token>.
Authorization (enforce permissions)
Building blocks
- Roles (RBAC):
admin,editor,viewer. - Scopes (OAuth): capabilities granted to a client (e.g.,
payments:write). - Permissions/Policies: fine‑grained rules; may include attributes (ABAC) or relationships (ReBAC).
Where to enforce
- Edge (gateway/middleware): reject obviously unauthorized calls early (no/invalid token; missing scopes).
- Inside services: enforce resource‑level rules (ownership, tenant). Defense in depth.
Tiny examples
Node/Express: verify JWT + scopes
import jwksRsa from "jwks-rsa";
import jwt from "express-jwt";
const checkJwt = jwt.expressjwt({
secret: jwksRsa.expressJwtSecret({ jwksUri: "https://idp.example.com/.well-known/jwks.json" }),
audience: "api://orders",
issuer: "https://idp.example.com/",
algorithms: ["RS256"]
});
function requireScope(s) {
return (req,res,next) => req.auth?.scope?.split(" ").includes(s)
? next()
: res.status(403).json({ error: "insufficient_scope" });
}
app.get("/orders/:id", checkJwt, requireScope("orders:read"), handler);
Policy example (OPA/Rego) — owner or admin
package authz
default allow = false
allow {
input.user.role == "admin"
}
allow {
input.resource.owner_id == input.user.sub
input.action == "read"
}
OAuth vs OIDC (often confused)
- OAuth 2.x / 2.1: framework for delegated authorization to APIs. Access Tokens are for resource servers.
- OpenID Connect (OIDC): identity layer on top of OAuth providing an ID Token and a standard user info endpoint → used for login.
- Don’t use OAuth alone for login. If you need “who is the user?”, use OIDC.
Roles, scopes, and permissions together
- Client → API: check scopes on the token (e.g.,
invoices:read). - User → Resource: check permissions/policies (e.g., “user is owner of invoice 123” or “has role
billing-admin”). - Keep role inflation in check; favor small, composable permissions grouped into roles.
Common mistakes & fast fixes
| Mistake | Why it’s bad | Fix |
|---|---|---|
| Treating OAuth as login | No stable identity; phishing risk | Use OIDC; validate ID Token |
| Storing tokens in localStorage | XSS steals tokens | Prefer HTTP‑only cookies or secure memory; never expose refresh tokens to JS in SPAs—use a BFF pattern |
| Not checking aud/iss | Token reuse against other APIs | Validate issuer and audience at the API |
| Using wildcard CORS with credentials | Browser blocks; leaks | Use specific origins + Allow‑Credentials: true |
| Missing CSRF protection with cookies | Cross‑site requests abuse sessions | Use SameSite, anti‑CSRF tokens, and double‑submit patterns |
| Overloading JWT as a DB | Huge tokens, stale claims | Keep tokens small; fetch fresh claims/server checks when needed |
| No token rotation/expiry | Long‑lived compromise | Short TTL access tokens + refresh rotation |
| AuthZ only at gateway | Bypass via internal paths | Enforce in service too (defense in depth) |
Quick checklist
- [ ] Pick OIDC for login; prefer Auth Code + PKCE.
- [ ] For browsers: HTTP‑only, Secure cookies; add CSRF protection.
- [ ] For APIs: check
iss/aud/exp/signatureand scopes. - [ ] Implement resource‑level authorization in services.
- [ ] Model roles/scopes/permissions clearly; log deny decisions.
- [ ] Protect tokens/secrets; rotate refresh tokens; monitor anomalies.
One‑minute adoption plan
- Write down your resources and actions (e.g.,
orders: read/write). - Choose OIDC IdP; implement Auth Code + PKCE login.
- APIs: add JWT validation and scope checks at the edge; add resource checks inside handlers.
- Store sessions in HTTP‑only cookies (web) or use Bearer tokens (native/CLI).
- Add CSRF, CORS, and logging/metrics for allow/deny.
- Review token TTLs and introduce refresh rotation & revocation where needed.