# Who are you, and what may you do?

Express course on authentication and authorization. 22 concepts — authN and authZ, password hashing, MFA, passkeys, sessions and JWT, OAuth 2.0 and OIDC, API keys, least privilege, RBAC, ABAC, ownership checks, IDOR and the other classic failures — each with its English name, a diagram, a picture, and a card with an example. At the end, a one-page cheat sheet of every concept.

- **Author:** Fedor Chishchin
- **Level:** intro
- **Reading time:** ~35 minute read
- **Published:** 2026-06-02
- **Source:** https://fedorthinks.com/learn/auth-express

---

## 1. Two questions, one word

Behind the word "auth" hide two different questions. Most security bugs come from confusing them — or from answering one and forgetting the other.

### Authentication (authN) — who are you?

> Showing your passport at the airport — proving you really are the person you claim to be.

This is the concept of **Authentication (authN)**, and it means proving identity: a login, a passkey, a token that says "this really is Alice." It usually happens **once per session.** Get it wrong and an impostor walks in as someone else. But proving who you are says nothing about what you may do — that's the other question entirely.

_Diagram: AuthN turns a credential (password, passkey, token) into an identity — and answers only the question “who is this?”_

#### Authentication (authN) (proving identity)

- **What it is:** Checking that the user is who they claim to be.
- **How it works:** The user presents a credential (password, passkey, token); the system verifies it and establishes an identity.
- **Example:** Email-and-password login: "this is Alice."
- **Catch:** Knowing who someone is doesn't tell you what they may do.
- **Related:** Feeds **Authorization**; done via **Password Hashing**, **MFA**, **Passkeys**, **OIDC**.

### Authorization (authZ) — what may you do?

> A hotel keycard — it proves nothing about your name, but it opens your room and not the others, and not the manager's office.

This is the concept of **Authorization (authZ)**: deciding what an identity is allowed to do — read this, edit that, never touch the admin panel. Unlike authN, it has to be checked **on every request**, because one logged-in user may touch a hundred different things. **Knowing who someone is is not permission to do anything in particular.**

_Diagram: AuthZ takes who, what action, and which resource — and on every request decides: allow or deny._

#### Authorization (authZ) (deciding permissions)

- **What it is:** Deciding what this identity may do with this resource.
- **How it works:** On every request: who + action + resource → rule → allow or deny.
- **Example:** Alice the editor may edit doc #42, but may not open the admin panel.
- **Catch:** Checking once and forgetting. It must be checked on **every request**, server-side.
- **Related:** Models — **Least Privilege**, **RBAC**, **ABAC**, **Ownership Check**; failure — **Broken Access Control**.

### Two layers — never collapse them

> A nightclub: the bouncer checks your ID at the door once, and the bartender still checks you're of age every time you order. Two checks, two moments.

The classic mistake is fusing the two — proving identity once and trusting that forever, or stuffing every permission into a login token and never re-checking. **AuthN happens at the door; authZ happens at every door inside.** Keep them separate, and check the second one constantly.

_Diagram: AuthN is one front door. AuthZ is a check at every inner door — that is, on every request._

### Don't Roll Your Own Auth — never build your own

> You don't weld your own bank vault from sheet metal in the garage — you buy one built and tested by people who do nothing else.

This is the practice of **Don't Roll Your Own Auth**. Identity is the foundation everything else stands on, and it's brutally easy to get subtly wrong. **Use proven standards and libraries** — established protocols, a battle-tested auth library or provider — not a clever scheme you invented. The history of security is paved with home-made auth that looked fine until it suddenly wasn't.

#### Don't Roll Your Own Auth (standards over DIY)

- **Problem:** Home-made auth looks fine until someone breaks it.
- **Idea:** Use proven protocols, libraries, and providers.
- **Example:** Instead of your own token scheme — OIDC and your framework's auth.
- **Cost:** You have to learn someone else's tool and wire it in correctly.
- **When to use:** Almost always.
- **Related:** Leads to **Auth Provider**; standards — **OAuth 2.0**, **OIDC**.

### The map: 22 concepts in six groups

> A keyring: first you see the whole bunch, then you learn which key opens which door.

The course's concepts fall into six groups. Each answers its own question: what the two "auths" are, how to prove identity, how to stay logged in, how to grant access without a password, how to decide permissions, and how it all usually breaks.

_Diagram: Six groups of concepts — chapters 1–6 follow them. Chapter 7 is how to choose, plus the cheat sheet._

Every concept below gets the same treatment: its **English name** (that's the term you'll search docs for), a picture from life, a **diagram**, and a **card** with fields. At the end, a cheat sheet with every concept.

> **Before any auth decision:** "Is this about proving who someone is — or deciding what they may do? They're not the same lock."

> Authentication is the door. Authorization is every door inside. Skip the second and the first means nothing.

## 2. Proving who you are

Authentication comes down to proving you're you. The factors range from the weak-but-universal password to the modern, phishing-resistant passkey.

### Password Hashing — passwords, the weak default

> A secret word shared with the doorman — fine until someone overhears it, guesses it, or you use the same one everywhere.

Passwords are universal and the weakest common factor: reused, guessable, phishable. If you must use them, the non-negotiable rule is **never store them as text**. Store a slow, salted **hash** (Argon2, bcrypt) — that's **Password Hashing** — so a stolen database doesn't hand over everyone's password. (More on that in the mistakes chapter.)

_Diagram: Password + random salt → slow hash → only the hash is stored. A stolen database gives up no passwords._

#### Password Hashing (salted slow hash)

- **Problem:** Passwords stored as text: one leak and the attacker has them all.
- **Idea:** Store not the password but a slow hash with a **salt** — a different one per user.
- **Example:** On login, compute Argon2/bcrypt of the typed password and compare.
- **Cost:** Login is a bit slower — on purpose.
- **When to use:** Always, if you store passwords.
- **Related:** The anti-example is **Plaintext / Fast Hash**; add **MFA** on top.

### MFA — something you know, have, are

> A safe-deposit box needs your key and the bank's key. One alone opens nothing.

**MFA (Multi-Factor Authentication)**, or two-factor, demands a second proof beyond the password — **something you have** (a code from an app, a security key) or **something you are** (a fingerprint). Even a stolen password is then useless alone. An authenticator-app code (**TOTP**) is solid; SMS codes are weak (interceptable) but better than nothing. **MFA is the single biggest, cheapest security win you can add.**

_Diagram: Three kinds of factors: know, have, are. Logging in takes at least two different ones._

#### MFA (Multi-Factor Authentication, 2FA)

- **Problem:** One stolen password = full access to the account.
- **Idea:** Require a second factor of a different kind: "have" or "are."
- **Example:** Password + a six-digit TOTP code from an authenticator app.
- **Cost:** An extra step at login; SMS codes can be intercepted.
- **When to use:** Wherever it matters; mandatory on admin accounts.
- **Related:** Strengthens **Password Hashing**; **Passkeys** are the next step.

### Passkeys — the passwordless future

> A key that only works at the one door it was cut for — fake the website, copy the lock's photo, and the key simply won't turn.

**Passkeys** (built on the **WebAuthn** standard) replace passwords with a cryptographic key pair tied to your device and the real site, so there's nothing to phish, reuse, or leak — and Apple, Google, and Microsoft now default to them. The private key never leaves your device; the site only ever sees a signature. **This is where authentication is heading: nothing to steal.**

_Diagram: The site sends a challenge, the device answers with a signature. A phishing site at a different address gets no signature._

#### Passkeys (WebAuthn, passwordless login)

- **Problem:** Passwords get phished, reused, and leaked.
- **Idea:** A key pair: the private key stays on the device, the site holds the public key. Logging in is a signature.
- **Example:** Signing in with a fingerprint or Face ID instead of typing a password.
- **Cost:** Needs device support and a fallback recovery path.
- **When to use:** As the main login wherever users are on modern devices.
- **Related:** Replaces **Password Hashing**; on its own, stronger than SMS **MFA**.

> **The authentication question:** "If this single factor were stolen, could someone log in as the user — and what's my second line of defence?"

> A password is a secret you can lose. A passkey is a proof you can't. Add a second factor either way.

## 3. Staying logged in

HTTP forgets you between requests, so after you log in the system needs a way to keep recognising you. Two approaches, with a real trade-off.

### Stateless HTTP — HTTP has no memory

> A shop where the clerk forgets you the instant you turn around — so you need a ticket to prove, on your next step, that you already paid.

HTTP is **stateless**: every request is independent; the server doesn't remember that you logged in a second ago. So after authentication you're handed something — a cookie or a token — that you send with each request to say "it's still me." Everything below is two ways to do that. (This is the statelessness from the protocols course.)

_Diagram: The server remembers no previous request, so every request carries a cookie or token with it._

#### Stateless HTTP (no memory between requests)

- **What it is:** The server doesn't remember the client's previous requests.
- **Consequence:** After login you need a "pass" that travels with every request.
- **Example:** The browser attaches the cookie to every request to that site by itself.
- **Related:** Two answers — **Session** and **JWT**.

### Session — the server remembers

> A coat check — you hold a little numbered ticket, and all the real information sits behind the counter under that number.

With **sessions**, the server stores who you are and hands you a cookie holding only a random **session id**. Each request, it looks the id up. The server is the source of truth, so logging out or banning someone is instant — just delete the session. The cost is that the server must store and look up that state. **The right default for most web apps.**

_Diagram: The cookie holds only an id. The server looks it up in the session store. Delete the session — the user is out at once._

#### Session (server-side session)

- **Problem:** HTTP doesn't remember who you are between requests.
- **Idea:** The server keeps the login data; the client holds only a random session id.
- **Example:** Cookie `sid=8f3a…` → in the store, `8f3a → Alice`.
- **Cost:** The server needs a store and a lookup on every request.
- **When to use:** A normal web app. This is the default.
- **Related:** The alternative is **JWT**; the id lives in an **httpOnly Cookie**; revocation is instant.

### JWT — the token carries the truth

> A festival wristband with your access level already printed and tamper-sealed on it — the guard checks the seal, not a guest list.

A **JWT (JSON Web Token)** is a signed token the client holds that _contains_ the identity and its **claims**; the server just verifies the signature, no lookup needed. That makes it stateless and easy to scale across services — but **hard to revoke:** a leaked token stays valid until it expires. So you keep lifetimes short and pair it with a **refresh token**. **Great for APIs and microservices; overkill for a simple web app.**

_Diagram: JWT = header.payload.signature. The API checks the signature without a database. A leaked token lives until exp — hence short lifetimes and a refresh token._

#### JWT (JSON Web Token)

- **Problem:** Many services, and making each one hit a shared session store is expensive.
- **Idea:** Put the identity and permissions into the token itself and sign it.
- **Example:** Payload `sub: alice, role: editor, exp: …`; each service verifies the signature itself.
- **Cost:** Hard to revoke: a leaked token is valid until it expires.
- **When to use:** APIs and microservices — a short-lived JWT + a **refresh token**.
- **Related:** The alternative to **Session**; the threat is **Token Leakage**.

### httpOnly Cookie — where you keep it matters

> A key in a locked drawer versus a key taped to the front door — same key, very different safety.

A token in the browser belongs in an **httpOnly cookie** (JavaScript can't read it), not in `localStorage`, where any injected script can steal it. The store is part of the security: get it wrong and one **XSS** bug hands an attacker every session. **How you transport and store the credential is as important as the credential itself.**

_Diagram: An injected script (XSS) can read localStorage, but cannot read an httpOnly cookie._

#### httpOnly Cookie (a cookie JavaScript can't read)

- **Problem:** A token in `localStorage` is stolen by any injected script.
- **Idea:** Keep the token in a cookie with the `httpOnly` flag — JS can't see it.
- **Example:** `Set-Cookie: sid=…; HttpOnly`.
- **Cost:** Almost none — it's the right default in the browser.
- **Related:** Holds a **Session** id or a **JWT**; guards against **Token Leakage**.

> **The session question:** "Do I need instant revocation and simplicity (sessions) — or stateless scale across services (short-lived JWTs)?"

> Sessions: the server remembers, and can forget you instantly. JWT: the token remembers, and you can't easily take it back.

## 4. Delegated access

Often you don't want to handle the password at all — you want users to sign in with an account they already have, or one service to act for another. That's what OAuth2 and OIDC are for.

### OAuth 2.0 — grant access without sharing a password

> A valet key that starts the car and opens the door, but not the boot or the glovebox — you hand over limited access, not your whole keyring.

This is the **OAuth 2.0** protocol. It lets a user grant your app limited access to their account on another service — your app reads their Google Calendar — **without ever giving you their Google password.** The other service hands your app an **access token** limited by a **scope** ("read calendar, nothing else"). It's about delegated _authorisation_, not identity.

_Diagram: The user logs in at Google and consents. The app gets a narrowly scoped token and calls the API with it. It never sees the password._

#### OAuth 2.0 (delegated authorization)

- **Problem:** An app needs access to someone's account elsewhere, but can't have the password.
- **Idea:** The owning service issues the app an **access token** with a narrow **scope**.
- **Example:** Your app reads the user's Google Calendar — and only that.
- **Cost:** A complex protocol; doing the token dance by hand is dangerous.
- **When to use:** Acting on a user's behalf in another service.
- **Related:** Doesn't say who the user is — that's **OIDC** on top.

### OIDC — OAuth2 that also says who you are

> The valet key, now with a photo ID clipped to it — not just "what this key opens," but "and here's who it belongs to."

OAuth2 alone tells you what an app may access, not _who_ the user is. **OpenID Connect (OIDC)** adds a thin identity layer — an **ID token** with verified claims (this is Alice, here's her email) — on top. This is what powers "**Log in with Google**" and **single sign-on (SSO)**. When you want social login or SSO, OIDC is the standard.

_Diagram: OIDC is a thin layer over OAuth 2.0: next to the access token (“what's allowed”) comes an ID token (“who this is”)._

#### OIDC (OpenID Connect)

- **Problem:** OAuth2 grants access but doesn't say who the user is.
- **Idea:** Add an **ID token** with verified identity data.
- **Example:** The "Log in with Google" button; signing into work tools via SSO.
- **Cost:** You depend on an external identity provider.
- **When to use:** Social login and single sign-on.
- **Related:** Built on **OAuth 2.0**; a way to do **Authentication**.

### API Key — the simple machine credential

> A spare key you give a trusted contractor — no name attached, just "whoever holds this may come in."

An **API key** is a long secret string that identifies a program, not a person — used for service-to-service and programmatic access. Simple and effective, but it _is_ the credential: anyone who finds it is in. So you scope it, rotate it, and **never commit it to git or put it in a URL.** Good for machines; not a substitute for real user identity.

_Diagram: The program sends the key in a header; the API checks the key and its scope. Never in git, never in a URL, rotated regularly._

#### API Key (machine credential)

- **Problem:** A program needs into an API with no human and no login.
- **Idea:** A long secret that identifies the program.
- **Example:** A backend calls a payments API with the key in a header.
- **Cost:** The key is the access: find it and you're in.
- **When to use:** Machine-to-machine. Not for logging in people.
- **Related:** Scope it by **Least Privilege**; the main threat is **Token Leakage**.

> **The delegation question:** "Do I need to act on a user's behalf elsewhere (OAuth2), know who they are via a provider (OIDC), or just identify a machine (API key)?"

> OAuth2 borrows access without the password. OIDC adds "and here's who they are." Use the standard — don't invent your own token dance.

## 5. What you're allowed to do

Once you know who someone is, you decide what they may touch. The models run from coarse roles to fine-grained policy — and the rule under all of them is deny by default.

### Deny by Default / Least Privilege — forbidden unless allowed

> A new employee's badge that opens nothing until each door is explicitly granted — rather than opening everything until someone remembers to lock a few.

The foundation of authorisation is two rules. **Deny by default:** everything is forbidden unless explicitly allowed. **Least privilege:** each identity gets the **minimum** access it needs, no more. Default-open systems leak — someone always forgets to restrict the new endpoint. Default-closed systems fail safe. **Start from "no" and grant deliberately.**

_Diagram: An explicit grant — let it through, and only to the minimum needed. No grant, or unclear — deny._

#### Least Privilege (deny by default)

- **Problem:** Someone forgot to lock the new endpoint — and it's open to everyone.
- **Idea:** "No" by default; grant only the minimum needed.
- **Example:** A new employee gets access to their own project only.
- **Cost:** You have to grant permissions explicitly more often.
- **When to use:** Always — it's the base of every permission model.
- **Related:** The foundation for **RBAC**, **ABAC**, **Ownership Check**.

### RBAC — permissions by role

> A theatre where your ticket type — stalls, circle, backstage pass — decides where you can go, without naming you personally.

**RBAC (Role-Based Access Control)** groups permissions into roles — admin, editor, viewer — and assigns users to roles. Simple, readable, and enough for most apps: "editors can publish, viewers can't." It gets coarse when rules depend on context or on the specific record, which is where the next models come in.

_Diagram: User → role → set of permissions. Permissions go to roles, not to people directly._

#### RBAC (Role-Based Access Control)

- **Problem:** Granting permissions to each person individually is chaos.
- **Idea:** Permissions go to roles; roles go to users.
- **Example:** "Editors can publish; viewers can only read."
- **Cost:** Coarse when a rule depends on context or the specific record.
- **When to use:** Most apps.
- **Related:** Finer-grained — **ABAC**; always add an **Ownership Check**.

### ABAC — permissions by policy

> A rule rather than a list — "managers may approve expenses under 10k, in business hours, from a company device" — evaluated fresh each time.

**ABAC (Attribute-Based Access Control)** decides from attributes of the user, the resource, and the context — role _and_ amount _and_ time _and_ device. Far more flexible than fixed roles, and far more complex. The signal to adopt it: you're stacking more than a couple of custom `if` conditions onto your role checks.

_Diagram: Attributes of the user, the resource, and the context go into a policy that decides afresh on every request._

#### ABAC (Attribute-Based Access Control)

- **Problem:** Permissions depend not just on the role but on amount, time, device.
- **Idea:** A policy rule over attributes of user, resource, and context.
- **Example:** A manager approves an expense under 10k, in business hours, from a work laptop.
- **Cost:** Much harder to write, read, and test.
- **When to use:** When more than a couple of custom `if`s are stuck onto your roles.
- **Related:** Grows out of **RBAC**; stands on **Least Privilege**.

### Ownership Check — can you touch this record?

> You have a valid library card (a role), but that doesn't let you read the notes scribbled in someone else's borrowed book.

The **ownership check** (object-level authorization) is the most-missed check: even a valid "editor" must only edit **their own** documents, not everyone's. The role says "editors may edit"; you still have to verify this editor owns _this_ row. Forgetting this is the single most common access-control bug — change the id in the URL and you're in someone else's data. **Always check the object, not just the role.**

_Diagram: Two checks: the role (“an editor?”) and ownership (“is this doc theirs?”). Allow only if both pass._

#### Ownership Check (object-level authZ)

- **Problem:** The role allows "edit documents" — and the user edits other people's.
- **Idea:** Besides the role, check that this object belongs to this user.
- **Example:** Before `PUT /docs/42`, check that doc 42's owner is the current user.
- **Cost:** An extra check on every object access.
- **When to use:** Always, whenever data has an owner.
- **Related:** Complements **RBAC**/**ABAC**; without it — **IDOR**.

> **The permissions question:** "Have I checked not just the role, but that this specific user is allowed to touch this specific thing?"

> Deny by default. Grant the least. And always ask not just "what role" but "whose record."

## 6. How it goes wrong

Auth fails in a handful of well-known ways — the same ones, over and over, in app after app. Knowing them is half the defence.

### Broken Access Control / IDOR — forgetting to check

> A building where the front door is guarded but every internal door is unlocked — once you're in, you can walk anywhere.

**Broken Access Control** is the number-one web vulnerability: an endpoint authenticates the user but **forgets to check they're authorised** for what they asked. Its commonest form is **IDOR (Insecure Direct Object Reference)** — changing `/orders/123` to `/orders/124` and seeing someone else's order, because the server never checked ownership. Breaches of millions of records trace to exactly this. **Check authZ on every request, server-side, against the actual object.**

_Diagram: Bob logged in honestly, but the server doesn't check ownership — change the id in the URL and you see someone else's order._

#### Broken Access Control / IDOR (missing permission check)

- **What it is:** The user is authenticated, but rights to the object aren't checked.
- **Looks like:** `/orders/123` → `/orders/124` — and someone else's order shows up.
- **Damage:** Breaches of millions of records.
- **Fix:** authZ on every request, server-side: `WHERE id = 124 AND owner = current_user`.
- **Related:** Cured by **Ownership Check** and **Least Privilege**.

### Plaintext / Fast Hash — storing passwords wrong

> Keeping the spare keys to every house on the street in a glass jar on the porch.

Storing passwords as **plaintext** — or with a fast hash like **MD5** — means one database leak exposes everyone, and reused passwords compromise their other accounts too. Passwords must be **slowly and individually hashed** (Argon2, bcrypt) so they're expensive to crack even when stolen. **The hash being slow is the point, not a flaw.**

_Diagram: A fast hash — cheap guesses, passwords cracked fast. A slow salted hash — every guess is expensive._

#### Plaintext / Fast Hash (passwords as text or MD5)

- **What it is:** Passwords in the database as-is or under a fast hash.
- **Damage:** One leak exposes every password; reused passwords open other accounts too.
- **Fix:** A slow salted hash: Argon2 or bcrypt.
- **Related:** The right version is **Password Hashing**.

### Token Leakage — leaking and trusting tokens

> Writing the alarm code on a sticky note stuck to the door — the lock is fine; you just handed out the secret.

**Token leakage** is how tokens and keys escape. They leak through **URLs (which get logged), browser `localStorage` (which a script can read), and secrets committed to git.** And a token must be _verified_, not trusted because it showed up — a tampered or expired one must be rejected. Treat every credential as something that will leak someday: short lifetimes, rotation, and never in a place that gets logged or scraped.

_Diagram: Three classic leak paths: URLs in logs, localStorage, and git. The defence: the server verifies every token, lifetimes are short, keys rotate._

#### Token Leakage (leaked tokens and keys)

- **What it is:** A credential ends up somewhere it can be read or found.
- **Looks like:** `?token=` in a URL, a token in `localStorage`, a key in a git commit.
- **Damage:** Whoever finds the token is in.
- **Fix:** Verify signature and expiry; short lifetimes, rotation; an **httpOnly Cookie** in the browser.
- **Related:** Hurts most with **JWT** and **API Key**.

### Trusting the Client — believing what the browser says

> A shop that lets customers write their own price tags and never checks them at the till.

This is the anti-pattern of **trusting the client**. Anything the client sends — a hidden form field, a role in a cookie, an `isAdmin=false` flag — **can be forged.** The browser, the app, the request are all under the user's control. Every real check must happen on the **server**, which the user can't edit. **The client is a suggestion; the server is the authority.**

_Diagram: Everything on the client side can be changed by the user. Only the server makes the decision._

#### Trusting the Client (client-side checks)

- **What it is:** Permission decisions made from data the client sent.
- **Looks like:** A role in a cookie, a hidden field, an `isAdmin` flag in the request.
- **Damage:** The user grants themselves permissions.
- **Fix:** Every check on the server; the client only suggests.
- **Related:** A common cause of **Broken Access Control**.

> **The failure question:** "If a user tampers with the id, the token, or the request, does my server still stop them?"

> Almost every auth breach is one of these: a check that was skipped, a secret stored badly, or the client believed.

## 7. Doing it right

Good auth is mostly about using proven tools correctly and checking permissions relentlessly — not cleverness. Here's how to choose.

### Auth Provider — use a library or a provider

> You don't manufacture your own seatbelts — you fit certified ones designed by specialists and crash-tested for years.

For almost everyone, the right move is to **not build auth yourself:** use your framework's auth, or an **auth provider** (Auth0, Clerk, Supabase, Cognito, and the like) that handles hashing, sessions, MFA, OAuth, and the edge cases you'd never think of. Your job is to wire it in correctly and own the **authorisation** — the part only you know.

_Diagram: The provider handles identity: hashes, sessions, MFA, OAuth. Your app owns the access rules._

#### Auth Provider (auth library or service)

- **Problem:** Hand-built auth almost always ends up with holes.
- **Idea:** Hand authN to a proven tool; keep authZ for yourself.
- **Example:** Auth0, Clerk, Supabase, Cognito, or your framework's built-in auth.
- **Cost:** A vendor dependency; it has to be wired in correctly.
- **When to use:** Almost always.
- **Related:** Puts **Don't Roll Your Own Auth** into practice.

### Match the mechanism to the app

> A house key, a car key, and a hotel keycard are all "keys" — you pick by the door, not by fashion.

**Sessions** for a normal web app (simple, instantly revocable); **short-lived JWTs plus refresh** when you need stateless scale across services; **OIDC** for social login and SSO; **API keys** for machine-to-machine. Add **MFA** everywhere it matters, especially admin accounts. Choose by the shape of your app, not by what sounds advanced.

_Diagram: The need on the left, the mechanism on the right. Choose by the shape of the app._

#### Before shipping auth

- **Are passwords slowly hashed** (Argon2/bcrypt), never stored as text?
- **Is MFA available**, and required for admins?
- **Is every endpoint checking authZ** — the role _and_ the ownership of the object?
- **Are tokens in httpOnly cookies**, short-lived, and never in URLs or git?
- **Is the real check on the server**, never trusting client-sent roles?
- **Am I using a proven library/provider** instead of a hand-rolled scheme?

#### Smell tests that it's broken

- Changing an **id in the URL** shows someone else's data.
- A **permission lives in a cookie or hidden field** the client can edit.
- Passwords stored in **plain text or MD5**.
- **No way to revoke** a logged-in user except waiting for expiry.
- **No MFA** on the accounts that can do the most damage.
- You **wrote your own** token or crypto scheme.

#### Signs you did it right

- AuthN and authZ are **separate**, and authZ is checked on **every request**.
- Every object access verifies **this user owns this thing**.
- Credentials are **hashed, scoped, short-lived, and rotatable.**
- The **server is the authority**; nothing trusts the client.
- You can **revoke access** and see **who did what** in a log.
- The heavy lifting sits in a **proven library or provider**, not your own code.

### How it all connects

> A hotel guest's path: a passport at reception, a keycard in hand, and every door decides for itself whether to open.

Login factors (password, MFA, passkey, OIDC, API key) lead to **authN**. Its result travels with every request in a **session** or a **JWT**, best kept in an **httpOnly cookie**. On every request, **authZ** decides by roles or policy — and always checks **ownership** of the object.

_Diagram: Left to right: factors → authN → session or JWT → authZ on every request → roles/policy + ownership._

## Cheat sheet: all 22 concepts

### Two questions

- **Authentication (authN)** (proving identity): Prove who you are. Usually once per login.
- **Authorization (authZ)** (deciding permissions): Decide what you may do. On every request.
- **Don't Roll Your Own Auth** (standards over DIY): Use proven standards and libraries, not your own scheme.

### Proving who you are

- **Password Hashing** (salted slow hash): Store a slow salted hash (Argon2, bcrypt), never the password.
- **MFA** (multi-factor, 2FA): A second factor: something you have or are. The cheapest big win.
- **Passkeys** (WebAuthn): A key pair on the device, login by signature. Nothing to phish.

### Staying logged in

- **Stateless HTTP** (no memory between requests): The server forgets requests — every one needs a pass.
- **Session** (server-side session): The cookie holds an id; the truth is on the server. Instant revocation. The default.
- **JWT** (JSON Web Token): A signed token with claims inside. No lookup, but hard to revoke.
- **httpOnly Cookie** (a cookie JS can't read): Where a browser token belongs. XSS can't read it.

### Delegated access

- **OAuth 2.0** (delegated authorization): Access to someone's account via a narrowly scoped token, no password.
- **OIDC** (OpenID Connect): OAuth2 + an ID token: "and here's who it is." Social login and SSO.
- **API Key** (machine credential): A secret for programs. Narrow scope, rotation, never in git or a URL.

### Permissions

- **Least Privilege** (deny by default): "No" by default; grant only the minimum needed.
- **RBAC** (Role-Based Access Control): Permissions by role: admin, editor, viewer.
- **ABAC** (Attribute-Based Access Control): Permissions by a policy over user, resource, and context attributes.
- **Ownership Check** (object-level authZ): Not just "what role" but "whose record."

### How it breaks

- **Broken Access Control / IDOR** (missing permission check): The check was skipped: change the id in the URL, see someone else's data.
- **Plaintext / Fast Hash** (passwords as text or MD5): One database leak — every password out.
- **Token Leakage** (leaked tokens): Tokens in URLs, localStorage, git. Whoever finds one is in.
- **Trusting the Client** (client-side checks): The client is a suggestion; the server is the authority.

### Doing it right

- **Auth Provider** (auth library or service): Hand authN to a proven tool; keep authZ for yourself.

> Auth isn't where you get clever. It's where you use standards correctly, deny by default, and never, ever trust the client.

End of express course · 7 chapters · 22 concepts

Next comes depth: the OWASP Top 10 and its cheat sheets, the OAuth 2.0 and OpenID Connect specs, and your auth provider's own guides. But carry the two questions everywhere — who are you, and what may you do? Answer the first with a proven tool, and the second on your server, every single time.
