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
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:
- Authentication answers "who are you?" — the login box, the password check, the session cookie.
- 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 answer401. 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