What is a token?
We just finished an entire section on sessions. The server remembers who you are. Your browser holds a random ID in a cookie. That random ID is the key to your user data on the server. We built the whole thing end to end. Now we are going to throw that mental model out and build the opposite. Welcome to tokens, the other major approach to authentication on the web. If you have heard the term “JWT” tossed around and had no idea what it actually meant, this is where it starts to click.
A different approach
Sessions work like this: the server remembers everything, the client just holds a reference. When you show your reference (the session ID), the server looks you up and figures out who you are.
Tokens flip that upside down:
With tokens, the client carries the identity itself.
Instead of a reference to data stored on the server, the client carries a document that contains the user data directly. The server does not need to look anything up. It just reads the document and verifies that it is genuine. If it is, great, here is who you are. If it is not, reject.
I love this analogy: sessions are like a library card. Tokens are like a passport.
A library card has a number on it. When you walk up to the counter and show it, the librarian types your number into their system to pull up your record: who you are, what books you have out, whether you owe fines. The card itself is useless on its own. All the real information lives in the library’s database.
A passport is different. A passport has your name, your photo, your nationality, your date of birth, all printed right on the document. When you show it to a border agent, they read it directly. They verify that the document is genuine (by checking the seals, watermarks, and holograms) but they do not need to look you up in some central database. The document carries everything needed to know who you are.
That is exactly the difference. Sessions = library cards. Tokens = passports.
How tokens travel in HTTP
Session IDs travel in cookies. Tokens do not. Tokens travel in a different HTTP header called Authorization. Here is what a request with a token looks like:
GET /me HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI0MiJ9.abc123signature That long, kind-of-scary string after Bearer is the token. It contains the user’s data (encoded, we will get to that) plus a cryptographic signature that proves the server is the one who created it. Let’s break down the pieces.
The Authorization header
HTTP has a standard header called Authorization. It is the conventional place for a client to send credentials. Unlike cookies, the browser does not attach this header automatically. Your client code (the JavaScript on the page, or a mobile app, or a curl command, whatever) has to set it explicitly on every single request.
This is a meaningful difference from sessions. Sessions ride on cookies and are handled by the browser invisibly. Tokens require the client to take responsibility for their own requests.
The Bearer scheme
The word Bearer before the token is called the authentication scheme. It tells the server what kind of credential is coming next. “Bearer” literally means “whoever bears (carries) this token should be granted access.” It is a statement that possession of the token is proof of identity, without any further challenge.
Other schemes exist. Basic is an old one for username/password in the header. But Bearer is the modern standard for token-based auth.
The exact format is:
Authorization: Bearer <token> One space between Bearer and the token. No quotes. No extra punctuation. Get this wrong and the server will reject the header.
Why a header instead of a cookie?
Cookies are automatic. The browser handles storage and attachment. That is super convenient for traditional web apps, but it requires a browser. If your client is a mobile app, a CLI tool, a server calling another server, or any other non-browser HTTP client, cookies are awkward or straight-up impossible.
The Authorization header works from any HTTP client. A mobile app can store the token in its secure storage and tack it onto requests. A CLI tool can take it as a flag. A microservice can include it when calling another microservice. No browser needed anywhere.
The tradeoff is that because the header is not automatic, the client is responsible for storing the token somewhere and attaching it to every request on its own. With cookies, the browser handled that for free. With tokens, you are handling it.
The login flow with tokens
Let’s trace the full flow end to end, the same way we did for sessions, so you can compare them:
1. POST /login { email, password }
→ Server verifies credentials
→ Server creates a signed token containing { userId: "42", role: "user" }
→ Server returns: { "token": "eyJhbG..." }
→ Server stores nothing
2. GET /dashboard
→ Client attaches: Authorization: Bearer eyJhbG...
→ Server reads the header, extracts the token
→ Server verifies the signature and checks expiration
→ Server reads the payload: { userId: "42", role: "user" }
→ Server returns the dashboard for User 42
3. Token expires (e.g. after 24 hours)
→ Client sends the expired token
→ Server rejects it (signature valid, but expiration passed)
→ Client must log in again to get a new token Do you see the key difference? Look at step 1, the last line: the server stores nothing. There is no session record. There is no lookup table. There is no server-side state at all. The token itself is the proof of identity, and the server only needs to verify that the signature is legit.
That is what people mean when they call JWTs “stateless.” The server has no state to maintain. Every request is self-contained. If you scale to 20 server instances behind a load balancer, it does not matter which one handles the request, because none of them need to look anything up.
What is inside a token?
A token contains three things:
- User data (called “claims”): user ID, email, role, etc.
- Metadata: when the token was issued, when it expires, what algorithm signed it.
- A signature: proof that the server created this token and nobody has tampered with it since.
The most common token format is JWT (JSON Web Token). In the next lesson, we are going to crack one open and look at exactly how these three parts are structured, how they are encoded, and (importantly) why a JWT is not encrypted even though it looks like gibberish.
What comes next
Over the next three lessons, we are going to:
- Dig into the structure of a JWT: what the three parts are and how they relate.
- Generate and sign JWTs on login using a library called
jose. - Verify JWTs to protect routes, mirroring the
authenticatefunction we wrote for sessions.
After that, we will put sessions and tokens side by side and compare their tradeoffs directly. Because here is the honest truth: there is no universal “better” answer between the two. They are tools for different jobs, and real apps often use both.
How does a token reach the server on each request?
Why do tokens use a header instead of a cookie?