Why this matters
Sooner or later you will design an API that someone else's app calls on behalf of a user — "log in with your account," a dashboard pulling from three services, a mobile app hitting your backend. The moment identity crosses a network boundary, two questions start following every request around: who is this? and are they allowed to do that? Get the first one wrong and strangers walk in. Get the second one wrong and a logged-in user reads someone else's invoices. Both mistakes show up in breach headlines, and both are avoidable once you see the shape of the problem.
Video: Authentication vs Authorization: What’s the Real Difference? — Redfox Security
Shows what weak authentication and broken authorization cost in real attacks.
One concert venue, two jobs
This lesson's running analogy: a big concert venue. Getting inside takes two separate operations, run by two different crews, and confusing them is the root of half the auth bugs you'll ever see.
Authentication (authN) is the ID check at the door. "Who are you?" The bouncer looks at your ticket, your photo ID, maybe your face — and decides whether you are really the person the ticket says you are. That's it. The bouncer doesn't care which stage you want to see. AuthN answers one question: are you who you claim to be?
Authorization (authZ) is the wristband system. Once you're in, a different crew looks at your wristband and decides where you're allowed to go: general admission stays on the lawn, VIP gets the front section, and the laminated "ALL ACCESS" badge gets backstage. The wristband crew doesn't re-check your ID — they trust the door did its job. AuthZ answers a different question: now that we know who you are, what are you allowed to do?
Say the mapping out loud, because interviewers love it: the ID check at the door is authentication, the wristband zones are authorization. A valid ticket (authN) never implied backstage access (authZ). When a system checks your login and then lets you read another tenant's data, somebody skipped the wristband crew.
Video: Authentication vs Authorization Explained Simply — Drewity
Visual explainer with a live demo contrasting 401 vs 403, each job mapped clearly.
Passwords: store them like you expect a breach
Every auth story starts the same way: a user hands you a secret — a password — and you have to verify it later without keeping a copy lying around. Because you should plan for the day someone steals your database. (They steal databases the way concert venues lose wristbands: constantly, and usually quietly.)
So the rule is absolute: never store passwords in plaintext. If a breach leaks your user table, plaintext passwords hand the attacker every user's credentials — including the ones people reused on their bank accounts. Instead you store a one-way hash of the password, salted per user:
- A salt is a random value, unique per user, mixed into the password before hashing. It defeats rainbow tables — precomputed dictionaries of common passwords — because the attacker would need a separate table for every salt. Store the salt next to the hash; it's not a secret, it's a fingerprint.
- Use a password-hashing algorithm that's deliberately slow: bcrypt or argon2. Normal hashes (SHA-256) are built for speed — a GPU can try billions of guesses per second. bcrypt has an adjustable cost factor, and argon2 (the winner of the 2015 Password Hashing Competition) is also memory-hard, which makes custom cracking hardware much less effective. Slowness is the feature: a login taking 200 milliseconds is invisible to a human and brutal to a brute-force attack.
flowchart LR
U["User types password"]:::client --> S["Add the user's unique salt"]:::security
S --> H["Slow hash: bcrypt or argon2"]:::security
H --> DB["Store only salt + hash"]:::data
The practical recipe, the one OWASP's cheat sheet endorses: on signup, hash the password with argon2id (or bcrypt with a cost factor around 10–12) and store the single encoded string the function returns. Both algorithms generate their own random salt and bake it into that string — there is no separate salt for you to juggle. On login, run the same verify function against the stored hash. If it matches, the bouncer nods them through. You never learn the password, and neither does anyone who steals your disk.
Video: Password Cracking Explained — See It Happen Live — The Exploit HQ
Cracks real hashes live, then shows bcrypt, Argon2, and salting as the defense.
Sessions vs tokens: two ways to remember a face
Once the bouncer has checked the ID, how does the venue remember you for the rest of the night? There are two classic answers, and the trade-off between them is one of those intermediate-level judgments that keeps showing up in real systems.
Interactive diagram: VsToggle (loads in the app)
The trade-off is honest, not ideological: sessions buy you easy revocation at the cost of a lookup and shared state; tokens buy you stateless scale at the cost of "you can't take back a stamp." In practice, most token systems split the difference with short-lived access tokens (minutes) plus long-lived refresh tokens the client trades in for new ones. Kill the refresh token and the access token dies on its own within minutes. It's the wristband that dissolves at midnight — the venue doesn't need a guest list if every pass self-destructs.
Video: Session vs JWT Tokens Explained in 6 Minutes | Authentication Architecture Made Simple — CodeWithNitin
Side-by-side tour of stateful sessions vs stateless tokens, and when to pick each.
JWT: a stamped hand, in three parts
A JSON Web Token is just the stamped-hand idea written in JSON. Three base64url-encoded chunks, joined by dots:
flowchart TD
T["eyJhbGciOi... · the token string"]:::security --> H["Header: signing algorithm + token type"]:::security
T --> P["Payload: claims — who, what, until when"]:::security
T --> S["Signature: computed over header.payload with the server's secret"]:::security
S --> V["Anyone holding the verification key can verify the signature (HS256: the shared secret; RS256: the public key). Tamper with a claim and verification fails."]:::security
The payload carries claims — small assertions like sub (the subject: user 42),
exp (expiry timestamp), iat (issued at), iss (issuer), aud (audience). The
signature (say, HMAC-SHA256 of header.payload with a secret only the server knows) is
what makes it trustworthy: the server re-computes the signature on every request, and if a
single character was changed, it won't match.
Two things about JWTs bite people again and again:
- Signed is not encrypted. Anyone who holds the token can base64-decode the payload and read it. Never put secrets, passwords, or anything sensitive in the claims. (The stamp is visible to everyone; it just can't be forged.)
- Verification is only as good as your key hygiene. If the signing secret leaks, the attacker can mint their own valid tokens. Rotate secrets, and for tokens that cross organizational boundaries prefer asymmetric signing (RS256): the issuer signs with a private key, and verifiers only need the public one.
This is the spec to know by name: RFC 7519 defines the JWT format, and you'll see its claim
names (exp, sub, aud) in half the auth libraries you ever touch.
Video: Your JWT Token, Decoded — Neural Download
Cracks open a real token piece by piece: header, payload, signature, plus the alg:none attack.
OAuth2: letting someone in without handing over the keys
Now the harder problem: a user wants a third-party app to act on their behalf — "let this photo-printing app read my cloud photos" — without giving the app their password. OAuth 2.0 (RFC 6749) is the protocol the entire internet settled on for exactly this. Here's the flow that matters, the authorization code flow, in venue terms: you tell the door your friend is allowed in; the door hands your friend a one-time claim ticket; your friend trades the ticket at the office — staff-only, behind the scenes — for a wristband.
sequenceDiagram
participant You as You (resource owner)
participant App as Third-party app
participant IdP as Identity provider
participant API as Photo API (resource server)
You->>App: "Let me connect my photo account"
App->>IdP: Redirect: authorize me? client_id=...&scope=read-photos
IdP->>You: Log in here (you authenticate directly)
Note over You,IdP: Your password never touches the app
You->>IdP: Approved — grant read-photos
IdP-->>App: Redirect back with a one-time authorization code
App->>IdP: Server-to-server: code + client_secret → tokens
IdP-->>App: Access token (and a refresh token)
App->>API: GET /photos with "Authorization: Bearer ..."
API-->>App: Your photos
Why the two-step dance with the code? Because the access token is the valuable thing, and
the code crosses through the browser — the noisy front channel. The code is short-lived,
single-use, and worthless on its own. The actual exchange happens server-to-server,
where the app proves its identity with a client_secret the browser never sees. Stealing
the code gets you nothing; you still need the secret and the back channel.
Two practical notes you'll need in real work:
- Scopes are the wristband zones of OAuth2:
read-photosvswrite-photosvsdelete-everything. Ask for the minimum your app needs — users notice, and so do security reviewers. - PKCE (pronounced "pixie") is the modern fix for apps that can't keep a secret — mobile and single-page apps. The app generates a one-time secret per login attempt and proves possession of it during the code exchange. If you're building a public client, PKCE isn't optional anymore.
Video: What is OAuth 2 — Dargslan
Walks the authorization code flow, scopes, and PKCE without drowning you in spec.
OIDC: OAuth2 learns to answer "who are you?"
Strictly speaking, OAuth2 is an authorization framework: it hands out wristbands, but it
never formally answers "who's wearing this one?" OpenID Connect (OIDC) is a thin identity
layer on top of OAuth2 that fixes that. After the authorization code flow, the identity
provider also returns an ID token — a JWT whose claims describe the user (sub, name,
email). "Log in with your account" buttons are OIDC under the hood: OAuth2 got the app
permission, OIDC told the app who granted it. One protocol for the wristband, one claim set
for the ID check — the venue analogy holds all the way down.
Video: OAuth 2.0 and OpenID Connect (in plain English) — OktaDev
Nate Barbettini explains how OIDC adds identity on top of OAuth, in plain English.
RBAC vs ABAC: who gets which wristband
Once identity is settled, somebody has to run the wristband crew. The two standard models:
- RBAC (role-based access control): permissions attach to roles, users get roles. Staff, VIP, General Admission. GitHub's repo roles (admin / write / read) are textbook RBAC: easy to audit, easy to explain, and occasionally too coarse — "VIP" either gets backstage or it doesn't.
- ABAC (attribute-based access control): permissions attach to policies over attributes of the user, the resource, and the environment. "Anyone with department=finance AND clearance≥2, on a company device, during business hours, may read this report." AWS IAM policies are ABAC in spirit. Far more expressive — and far easier to misconfigure into a policy nobody can fully audit.
The honest intermediate take: start with RBAC — roles are the wristbands everyone understands. Reach for ABAC when "VIP or not" stops being expressive enough, and accept that you're trading simplicity for a policy engine you'll need to test like code. Because it is code.
Video: Role-based access control (RBAC) vs. Attribute-based access control (ABAC) — IBM Technology
An IBM Distinguished Engineer compares role-based and attribute-based models with real trade-offs.
Where auth lives when you have fifty services
In a microservices world, you don't want every service re-checking IDs at its own little door — fifty bouncers, fifty chances to get it wrong. The standard shape is to check once, at the edge:
flowchart LR
U["Browser / mobile app"]:::client --> G["API gateway: validates the token"]:::security
G --> O["Order service"]:::service
G --> P["Payment service"]:::service
O --> OD["Orders DB"]:::data
P --> PD["Payments DB"]:::data
The API gateway terminates auth: it validates the JWT (signature, expiry, audience), rejects bad tokens with a 401, and forwards the request downstream with the caller's identity attached — typically as headers or a signed internal token carrying the claims. Downstream services then do authorization: the order service checks the wristband claims ("is this user allowed to read order 42?") without re-validating the signature. AuthN once at the edge, authZ everywhere, each service owning its own wristband rules.
One caveat worth its weight: never trust identity claims that arrive from the client
directly — only the ones your gateway validated and stamped. A service that accepts a
X-User-Is-Admin: true header from the open internet has fired its bouncer and hired a
sign that says "admins this way."
Video: How Authentication & Authorization Work in Microservices (JWT + API Gateway) | Lecture - 15 — Base2Coder
Walks the end-to-end flow: an auth service issues JWTs, the gateway validates at the edge.
Takeaways
- Authentication answers who are you? (the ID check at the door); authorization answers what may you do? (the wristband zones). A valid identity never implies permission.
- Never store plaintext passwords. Salt each one, hash it with a deliberately slow algorithm — bcrypt or argon2 — and plan as if your database will leak.
- Sessions are stateful and easy to revoke; JWTs are stateless and easy to scale, but revocation is the hard part. Short-lived access tokens plus refresh tokens split the difference.
- A JWT is header, payload, and signature — signed, not encrypted. Verify the signature every time, keep secrets out of the claims, and guard the signing keys.
- OAuth2's authorization code flow keeps the valuable tokens on a server-to-server back channel; OIDC adds an ID token so the app also learns who logged in. Ask for minimal scopes, use PKCE for public clients.
- Start with RBAC; reach for ABAC when roles stop being expressive enough. Check authN once at the API gateway, enforce authZ in every service — and never trust a claim the client handed you itself.
Check your understanding
In the concert-venue analogy, checking a guest's photo ID at the door is an example of...
- Authentication — it verifies they are who they claim to be
- Auditing — it records their visit for later review
- Authorization — it decides which stage they may visit
- Encryption — it scrambles their identity for privacy
Why is revoking access harder with JWTs than with server-side sessions?
- JWTs are stored in the database, which is slower to update
- JWT signatures cannot be recomputed after login
- There is no central record to delete — a signed token stays valid until it expires
- Session cookies are encrypted but JWTs are not
In the OAuth2 authorization code flow, what does the third-party app exchange — server-to-server, with its client secret — to get the access token?
- A refresh token it generated itself
- The ID token from OpenID Connect
- The user's password
- The short-lived, single-use authorization code
Your system needs a rule like 'finance staff may read this report only on company devices during business hours.' Which access model fits best?
- Password salting — it protects the report at rest
- ABAC — the decision depends on attributes of the user, device, and environment
- OAuth2 scopes — they already cover time-of-day restrictions
- RBAC — assign everyone a finance role and be done
Go deeper
Want to keep pulling this thread? These talks and tutorials go further than we did here:
- Everything You Ever Wanted to Know About OAuth and OIDC — Aaron Parecki (OAuth 2.1 spec co-editor), online session (~45 min). Grant types, JWT trade-offs, and scopes, explained by a spec editor.
- OAuth Sketch Notes Q&A — PKCE, Scopes, Security, Passwordless — Aaron Parecki & Lee Brandt, Okta Developer (~60 min). PKCE, SPA token risks, and hosted versus embedded login.
- Oktane18: Using OAuth and OpenID Connect in Your Applications — Aaron Parecki & Padma G., Okta (Oktane18). Auth versus authz, password hashing, and the code and refresh flows.
- OAuth 2.0 Course for Beginners — freeCodeCamp.org (~1.5h). The full OAuth 2.0 flow — roles, authorization code + PKCE, resource server, client app, JWKS — hands-on.
- System Design Course – APIs, Databases, Caching, CDNs, Load Balancing & Production Infra — freeCodeCamp.org (~2h 05m). Its authn/authz chapters place these flows inside a production stack.
Sources & further reading
- RFC 7519, "JSON Web Token (JWT)," IETF, May 2015 — the token format: header, payload, signature, and registered claims.
- RFC 6749, "The OAuth 2.0 Authorization Framework," IETF, October 2012 — authorization code flow, scopes, tokens, and refresh tokens.
- OpenID Foundation, "OpenID Connect Core 1.0," 2014 — the identity layer on OAuth2: ID tokens and the
subclaim. - OWASP, "Authentication Cheat Sheet," OWASP Cheat Sheet Series — password storage, session management, and credential hygiene in practice.