// security · auth

How auth works: authentication vs authorization

Every application asks two different questions: who are you, and what may you do? This guide walks a real login flow — 401s, password checks, session cookies, 403s, and role-based permissions — with simulators for both halves. No framework required; every framework implements exactly this.

mindmap — quick refresh auth — two different questions authentication — who are you? credentials → server verifies (hashes, never plaintext) session cookie = your badge missing/failed → 401 (apps often redirect to /login) authorization — what may you do? checked on EVERY request roles → permissions denied → 403 401 vs 403 401: prove your identity first 403: identity known — the answer is still no in practice every framework ships this — don't hand-roll your real job: design the permission matrix the check must live on the server

Prerequisites: How HTTP works — especially the cookies/sessions and status-code sections; this guide builds directly on both.

Two words that get mixed up constantly, and one distinction that untangles them:

  1. Authentication answers "who are you?" — the login box, the password check, the session cookie.
  2. Authorization answers "what may you do?" — and it's asked again on every single request, long after login.

They fail differently too: fail authentication and you get 401 Unauthorized ("prove your identity"); fail authorization and you get 403 Forbidden ("we know exactly who you are — no"). If you've read How HTTP works, you already have all the machinery: this whole topic is status codes plus cookies.

Authentication: proving who you are

HTTP is stateless — the server forgets you between requests. So authentication has two jobs: check your credentials once, then give you something to carry that proves the check happened. That something is a session cookie. Watch a full login:

Three details that matter in real systems:

  • The server never stores your password. It stores a hash — a one-way fingerprint. At login it hashes what you typed and compares fingerprints. A database leak then exposes fingerprints, not passwords. (Every serious framework does this by default.)
  • The session cookie is your badge, not your identity. The server keeps a table mapping sessionid → user; the cookie is just the claim ticket. That's why "log out everywhere" works — the server deletes its side of the mapping and every badge goes dead.
  • Browser apps redirect to /login (302); APIs answer 401. Same meaning, different audience: humans get a form, programs get a status code.

Authorization: what you may do

Being logged in is not permission. After authentication, every request still passes a second gate: does this user have the right to do this thing? The standard way to manage that is roles — bundle permissions into named jobs, assign users to roles:

Note what the simulator shows: the professor can delete the course but can't submit an assignment — authorization isn't a ladder where higher roles do everything, it's a matrix of who-does-what. And the check lives on the server: hiding a button in the UI is cosmetics, not security. A student who crafts a raw DELETE /course/42 request must still hit a 403.

401 vs 403: the debugging rule

  • 401 Unauthorized → the server doesn't know who you are (or your session expired). Fix: authenticate. Misnamed by history — it really means unauthenticated.
  • 403 Forbidden → the server knows who you are and the answer is no. Fix: get the permission, or stop asking.

When an app misbehaves, this pair tells you instantly which half of auth to debug.

Every framework ships this — your job is the matrix

None of the above is exotic: every serious web stack implements the same two gates, whatever the language. Stripped of syntax, the server-side logic is always:

on every request:
    user = session_table[ request.cookie("sessionid") ]     # authentication
    if user is missing:
        return 401  (or redirect to /login)

    if not permitted(user.role, request.action):            # authorization
        return 403

    handle the request

Django calls it contrib.auth, Spring calls it Spring Security, Rails has Devise, Laravel has guards and policies — same machinery, different names. Two consequences:

  • Don't hand-roll it. Password hashing, session management, and permission checks are exactly where home-made code gets breached. Use what your framework provides.
  • The part only you can do is design the permission matrix — the roles × actions table from the simulator, for your application's real actions. Sketch it before you code; it's a design document, not an afterthought.

Takeaways

  • Two questions, two gates: authentication once at login ("who are you?"), authorization on every request ("may you do this?").
  • 401 = prove identity; 403 = identity proven, still no. Memorize the pair — it's your fastest debugging signal.
  • Passwords are stored as hashes, never plaintext. If a tutorial stores plaintext, close the tab.
  • The session cookie is a claim ticket, meaningful only against the server's session table.
  • Authorization lives on the server. Hidden buttons are UX; the 403 is the security.
  • Use your framework's auth machinery — whatever the stack — and spend your effort designing the permission matrix instead.

References

  • Barth, A. (2011). HTTP state management mechanism (RFC 6265). Internet Engineering Task Force. https://doi.org/10.17487/RFC6265
  • Fielding, R., Nottingham, M., & Reschke, J. (2022). HTTP semantics (RFC 9110). Internet Engineering Task Force. https://doi.org/10.17487/RFC9110
  • MDN Web Docs. (n.d.). HTTP authentication. Mozilla. Retrieved August 16, 2026, from https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Authentication
  • OWASP Foundation. (n.d.). Authentication cheat sheet. Retrieved August 16, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
  • OWASP Foundation. (n.d.). Authorization cheat sheet. Retrieved August 16, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html