How Login Works — Authentication, Sessions, and Cookies

This article is part of the IT Foundations course, which builds up from scratch the practical IT knowledge you need at a minimum for programming and vibe coding.
A login feature is made of authentication, which confirms that a user really is who they claim to be, and a session, which lets the server keep recognizing that same user afterward. The diagrams show what the hash and the Cookie each do.

This article covers how a login feature works.

A login feature is made of two mechanisms: one that confirms a user is who they claim to be, and one that keeps recognizing that same user afterward.

"Hashing," "session-based authentication," and "JWT" are different names for parts of one login feature.

A login feature is made of two mechanisms
tanakalogs inConfirm whothe user isKeep recognizingthe same userCompare passwordhashesSession IDin the Cookie
The single login on the left splits into an upper and a lower branch. The upper one is the check done only once at the start; the lower one is the check done on every request after that. The right column shows the terms this article covers.

The upper branch is the check done only once; the lower branch is the check done on every request after that.

Authentication confirms who you are; authorization decides what you may do

Authentication — confirming that a user really is who they claim to be — is the decision made at login.

Authorization — deciding what that confirmed user is allowed to do — is a separate decision made afterward.

The two decisions the server makes within one request to delete a booking
One request: "delete this booking"
Decision 1 — authentication (is this really tanaka?)
  • What it uses — ID and password; after login, the session ID in the Cookie
  • If it fails — send the user back to the login screen
  • The password is checked once; after that, the session ID
Decision 2 — authorization (may this booking be deleted?)
  • What it uses — who owns the booking, and whether the user is an admin
  • If it fails — return "you do not have permission"
  • Checked every time an action is taken
The outer box is one request. Inside it are two decisions, in order from the top. Even when the upper one passes, the lower one can still refuse.

Even for actions by the same tanaka, the two are decided separately.

The same tanaka can be stopped at authentication or at authorization
Open bookingswithout logging inLogged in, deleteyour own bookingLogged in, deleteanother's bookingFailsauthenticationPasses authenticationPasses authenticationNever reachesthe decisionYou are the owner→ allowedOwner is another→ refusedLogin screenDeletedNo permission
Each row is one action. It goes through authentication and then authorization from the left, and at the point where it fails, the screen on the right is returned. The top row never reaches authorization.

Some rows stop at authentication, and some pass authentication and then stop at authorization.

Settings like "admins only" and "you can see only your own data" are authorization.

Authentication confirms who you are; authorization decides what you may do

Being able to log in and being allowed to perform an action are decided separately.

Authentication confirms whether a user is who they claim to be, authorization decides whether that user may perform that action, and authorization is checked on the server every time an action is taken.

Passwords are stored as hashes, and hashes are compared with hashes

The first thing to understand about authentication is that the server does not store the password itself.

What is stored is a hash — a string produced from the original string by a fixed procedure, which cannot be turned back into the original.

What you typeHashed on the spotCompared with the stored hashResult
hanabi2026 (correct)a3f9...c1Same as a3f9...c1Login succeeds
hanabi2025 (one character different)7b20...e4Different from a3f9...c1Login fails
Hanabi2026 (different capitalization)d15c...8aDifferent from a3f9...c1Login fails

A single different character produces a completely different hash.

The check still works even though the hash cannot be reversed, because what it is compared against is also a hash.

All the server holds is a string that cannot be reversed.

What is stored in the database and what is not
my-app's database
One row in the users table (tanaka)
  • email — tanaka@example.com
  • password_hash — a3f9...c1
  • created_at — the date and time the account was created
What this row does not hold
  • The typed characters hanabi2026
  • Any data that can be turned back into the typed characters
  • Whether the user is currently logged in
Inside the my-app database. tanaka's single row holds only an email address, a hash, and the sign-up date and time.

Passwords are stored as hashes in case the contents of the database leak.

Commonly used strings can be guessed, so a different string for each user (a salt) is added before hashing.

HTTPS protects the data from being read while it travels, which is separate from the form it is stored in.

What happens next depends on whether the hashes match
tanaka pressesLog inCompare thehashesMatchedDid not matchCreate a record,return a session IDReturn the loginscreen again
The single login on the left splits up or down according to the result of the comparison. Only when they match is a record created inside the server.

What is stored is a hash, and what it is compared against is a hash too

The server does not hold the password the user typed.

All it holds is a string that cannot be reversed, and at every login it runs the typed characters through the same procedure and checks whether the resulting string is the same as the stored one.

After login, the session ID in the Cookie is how the server knows it is the same person

Even after a successful login, the request that opens the next page is a separate request.

HTTP is stateless — one exchange does not remember the previous one — so the server does not keep track of who logged in a moment ago.

The fact that the login passed is recorded on the server, and the browser is handed a string that points to that record.

The record kept on the server is the session, the string that points to it is the session ID, and the mechanism that hands that string to the browser and has it sent every time is the Cookie.

What the browser and the server each hold after login
my-app after login
Inside tanaka's browser
  • Cookie: session_id=3f9a1c...e81b
  • Attached automatically to every request to the same site
  • Nothing besides this string is held
Inside the server
  • Session record 3f9a1c...e81b is tanaka
  • This record has an expiry
  • Logging out deletes this record
The same string, 3f9a1c...e81b, ties the browser side to the record on the server side. The name and the permissions exist only on the server side.

All that is on the browser side is the string 3f9a1c...e81b, which holds no name and no permissions.

Whose login it is becomes clear only when that string is matched with the record on the server.

Only the upper row is matched with a record, so the booking list comes back without asking for the password again.

This hand-off takes the form of the following two lines.

# The line the server returns when login succeeds
Set-Cookie: session_id=3f9a1c...e81b; HttpOnly; Secure

# From then on, the line attached to every request the browser sends to the same site
Cookie: session_id=3f9a1c...e81b

The two items after Set-Cookie are instructions to the browser.

HttpOnly keeps the JavaScript on the page from reading this Cookie, and Secure makes it sent only over HTTPS.

The contents of a Cookie can be rewritten by the user, so the result changes depending on whether they are trusted as they arrive.

The result changes depending on whether the Cookie value is trusted as it arrives
is_admin=true(claims admin)added and sentTrust the Cookievalue as sentThe adminscreen appearsRead the recordfrom session_idStays a regularuser
When a Cookie rewritten by the user arrives. The upper path decides from the value that arrived; the lower path decides from the record on the server.

A Cookie is stored in the browser, so the user can rewrite its contents and send it.

Put only the session ID in the Cookie, and make the decision from the record on the server.

The box on the left holds just one string with no meaning of its own.

Everything that decides who a user is and what they may do sits inside the box on the right.

The browser is handed just one string that points to the record

At the moment a request arrives, the server does not know who it is from.

It finds out because the record created at login and the string the browser sends every time are matched, and if the record is deleted or expires, sending the same string takes you back to the login screen.

Session-based authentication vs JWT — does the server hold the record, or does the string?

"Session-based or JWT" is a choice about which side holds the proof of being logged in.

The other approach leaves no record on the server and gives the proof itself to the browser.

The string of proof attached and sent every time is a token, and JWT (JSON Web Token), a string that bundles a user ID, an expiry, and a signature, is the best-known format.

What is inside the one JWT the browser is handed
One string the browser attaches and sends every time (JWT)
Contents — whose it is, and until when it is valid
  • tanaka's user ID
  • Expiry (usable until this time)
Signature — a value only the server can produce
  • A string computed from the contents
  • Rewriting the contents makes the computation stop matching
The outer frame is the one string sent every time. The contents sit inside in a form you can read, and the signature is what detects any rewriting.

Because of the signature, the server can tell that the string that arrived is one it issued, without checking it against a record.

AspectSession-based authenticationJWT
What is handed over on a successful loginsession_id=3f9a1c...e81beyJhbGci... ID, expiry, and signature
Where the basis for the decision sitsA record inside the serverThe contents inside the string
How it is checked each timeMatch it against the recordVerify the signature

A JWT carries its contents, so it can be accepted without a record on the server.

With session-based authentication, deleting the record stops the next request from passing.

A JWT passes until its expiry, so when you want to stop it immediately, you keep a separate list of the ones you have revoked.

This article drew session-based authentication as a Cookie and JWT as a string, but in practice there are setups that put a JWT in a Cookie.

Even when where it is kept changes, the difference — matching against a record or verifying a signature — stays the same.

Does the server hold the record, or does the string hold the contents itself?

In both approaches, the browser attaches and sends one string every time.

What differs is what the server does when it receives it: session-based authentication looks up its own record, while JWT verifies the signature attached to the string.

QUIZ

Knowledge Check

Answer each question one by one.

Q1When the server stores a user's password, what is placed in the database?

Q2When you open another page after logging in, why does the server know it is the same user?

Q3A logged-in user tried to delete someone else's booking and was refused by the server. Which decision is this?