Skip to content

Who are you: sessions, cookies, JWT

How a server knows who is calling: a session ID in a cookie, or a signed token (JWT). How long each one lasts, and how you cancel a login before it ends.

  • Lesson 99 of 125
  • 24 min read
  • Core
  • Free to read, no account
On this page
  1. Why interviewers ask this
  2. The idea in plain words
  3. Pause and predict
  4. Worked example
  5. Trade-offs
  6. Common misconceptions
  7. How to say it in the interview
  8. Check your understanding
  9. Answers
  10. Sources

What you need first

This lesson uses ideas from these earlier lessons. Read them first if any idea is new to you.

01Why interviewers ask this

Almost every design has a login. The interviewer asks: "How does the server know who is calling?" Then comes the harder question: "A user's phone is stolen. How do you block it?"

A weak answer is "we use a token" or "we use cookies", with no reason. A strong answer says where the proof of login is kept, how long it lasts, and how you cancel it early. It also shows what each choice costs.

At SDE-1 level you should explain a session and a token. At SDE-2 level you should explain expiry and the cancel problem, with numbers. At SDE-3 level you should also say what breaks when server clocks differ, and which risk you accept.

02The idea in plain words

Think of a college campus with many gates. There are two ways to let students in.

Way 1. At the first gate, the guard checks your ID card and gives you a numbered token. The guard writes your number and name in a register that every gate can read. At each gate, the guard looks up your number. If you lose the token, the guard crosses out the number, and it stops working at once. But every entry needs a look in the register.

Way 2. At the first gate, the guard gives you a card. It has your name and an end date, and a seal that is hard to copy. Each gate checks the seal and the date. There is no register, so entry is fast. But if you lose the card, no gate can cancel it. It works until the end date.

Way 1 is a session. Way 2 is a token.

Login and the proof

Authentication means proving who you are. Login is the first proof. HTTP forgets you between requests (lesson 7), so each next request must carry proof again. If the proof is missing or wrong, the API answers 401 (lesson 9).

The server must never store your password. It stores a hash: a one-way scramble. You can make a hash from a password, but you cannot get the password back from the hash. At login, the server hashes what you typed and compares. Use a library and a slow hash made for passwords, such as Argon2id or bcrypt.

Sessions: the proof stays on the server

After a good login, the server makes a session ID: a long random code. It must be hard to guess, so use at least 64 bits of randomness. The server saves a session record (user ID, start time, end time) in a shared store, as in lesson 26. It sends the ID to the browser in a cookie. On each request, the server reads the ID from the cookie and looks up the record. No record means 401.

The cookie has settings, defined in RFC 6265 (an internet standard):

  • Secure: the browser sends the cookie only over HTTPS.
  • HttpOnly: scripts that run in the page cannot read the cookie.
  • Max-Age: the life of the cookie, in seconds.

Make a new session ID right after login, so that an ID known before login is useless.

A session has two time limits. The idle timeout ends it after some time with no requests. The absolute timeout ends it after a fixed total time, even if the user is active. OWASP, a non-profit group that publishes free security guides, says common idle timeouts are 15 to 30 minutes for low-risk apps and 2 to 5 minutes for high-value apps. For an office app used all day, it says 4 to 8 hours is a suitable absolute timeout.

To log out, or to block a stolen session, delete the record. The next request fails. Clearing the cookie in the browser is not enough, because the ID still works until the server deletes the record.

Tokens: the proof travels with the request

A JSON Web Token (JWT) has three parts joined by dots: header, payload and signature. Each part is written in base64url, a way to write bytes as plain letters and digits. The header names the signing method. The payload holds claims: named facts. The signature is a code made from the header, the payload and a secret key (this is the common shared-secret method). It is the same idea as the pre-signed URL of lesson 24. A typical payload:

json
{"sub":"u_42","iat":1792506600,"exp":1792507500}

sub is the user ID. iat is the time of issue. exp is the time after which the token must be refused. Times are in Unix time: the number of seconds since 1 January 1970. Here exp minus iat is 1,792,507,500 - 1,792,506,600 = 900 seconds = 15 minutes. A token can also have nbf ("not before"), a time before which it must be refused.

To check a token, the server makes the signature again with the secret key. If the codes are equal and the time is before exp, the token is good. There is no lookup, so any server with the key can check it. Three rules:

  1. A JWT is signed, not encrypted. Anyone can decode the payload and read it. Put no secrets in it.
  2. Always check the signature. Name the allowed signing method in your code, and refuse a token that says its method is none (RFC 8725, the security advice for JWT).
  3. Keep the token in an HttpOnly cookie, so page scripts cannot read it. This is design advice, not part of the JWT standard.

Expiry and the clock

The server checks exp with its own clock. Clocks differ (lesson 49). A server whose clock is 20 minutes ahead sees every new 15-minute token as already expired. A server whose clock is 2 minutes behind may see a token with nbf as "not valid yet". The JWT standard lets a server allow a small leeway, "usually no more than a few minutes". A leeway (a small extra time) of 30 to 60 seconds hides small differences. It cannot hide big ones. The real fix is to keep every clock correct.

The cancel problem

To revoke a login is to cancel it before its normal end: at logout, after a theft, or when an admin blocks a user. A session needs one delete. A JWT has nothing to delete, because every server can check it alone. It stays valid until exp. There are three fixes:

  1. A short access token and a refresh token. The access token lives 15 minutes. A refresh token is a long random code that the server stores, like a session ID. When the access token ends, the app sends the refresh token. The server checks its record and gives a new access token. To revoke, delete the refresh record. A stolen access token then dies within 15 minutes.
  2. A denylist. Keep a list of revoked token IDs (the jti claim, a unique ID in each token) until they expire. Each request checks the list. The list stays small, because an entry can leave when its exp passes. But state is back, and every check needs it.
  3. Use sessions.
QuestionSessionJWT
Where is the proof?In the server store. The cookie holds an ID.In the token
Cost of a checkOne lookupA signature check, no lookup
Cancel now?Yes, delete the recordNo, wait for exp (or use a denylist)
Bytes per request (sizes assumed below)The ID, 32 bytesThe token, about 300 bytes

Stuck on something? The AI tutor has read this lesson. Ask it with a free account

03Pause and predict

Pause. Guess first.

A user's phone is stolen. The user taps "log out everywhere". How soon is the thief blocked if the login is a session record in a shared store? How long can the thief use the phone if the login is a 15-minute JWT and nothing else changes?

Write your guess before you read on. The answer is in the worked example.

04Worked example

We use the food-ordering app of lesson 3, with the numbers of lesson 26.

Assumptions (made up to teach with):

  • 500,000 users are logged in at the peak, and the peak is 20,000 requests per second.
  • The shared store does 100,000 operations per second. A session record is 1 KB. A lookup adds 0.5 ms.
  • A session ID is 16 random bytes written as 32 characters, so the cookie adds 32 bytes.
  • A token is 300 bytes and lives 900 seconds (15 minutes). Checking a signature takes 0.05 ms of CPU.
  • A refresh token lives 30 days. There are 12 API servers, and requests are shared evenly.

Step 1: sessions.

  • Lookups: 20,000 per second, which is 20,000 / 100,000 = 20% of the store.
  • Memory: 500,000 x 1 KB = 500 MB.
  • Extra data: 20,000 x 32 bytes = 640,000 bytes per second, which is 0.64 MB, or 5.1 megabits per second.

Step 2: tokens.

  • Lookups: none.
  • Extra data: 20,000 x 300 bytes = 6,000,000 bytes per second = 6 MB = 48 megabits per second. That is 300 / 32 = 9.4 times more than the session ID.
  • CPU: 20,000 x 0.05 ms = 1,000 ms of work per second, which is one core.

Step 3: the stolen phone. With a session, the delete takes effect on the thief's next request, which gets 401. With the token, the thief can use it until exp: at most 900 seconds, which is 15 minutes. This answers the predict box. If the token lived 24 hours, the window would be 24 x 60 / 15 = 96 times longer.

Step 4: add a refresh token. Each of the 500,000 users asks for a new access token every 900 seconds. That is 500,000 / 900 = 556 lookups per second (rounded from 555.6). It is 556 / 20,000 = 2.8% of the lookups of the session design. To block the user, delete the refresh record. The thief's window is at most 15 minutes, and not 30 days.

Step 5: one wrong clock. One of the 12 servers runs 20 minutes ahead. A new token has exp = issue time + 15 minutes. That server reads issue time + 20 minutes, so it refuses every token. A leeway of 60 seconds does not help, because 20 minutes = 1,200 seconds is more than 900 + 60. So 1 / 12 = 8.3% of requests get 401, and a retry often works because it lands on another server. Group the failures by server to find the cause.

05Trade-offs

  • Sessions solve instant cancel: one delete. They cost one lookup per request (20% of the store here, and 0.5 ms), and a store that must stay up. I would use them for a normal website. I would change when many separate services must check logins without calling one store.
  • A JWT solves a check with no lookup, by any server that has the key. It costs no cancel before exp, about 9 times more bytes per request than a session ID, and a payload that anyone can read. I would change to a session if I must block users at once.
  • A short token with a refresh token solves most of the cancel problem. It costs a stored record again (556 lookups per second here) and more code. I would change the 15 minutes to a shorter time if a stolen token costs a lot.

06Common misconceptions

"A JWT is encrypted, so I can put the user's phone number and role in it." A JWT is signed, not encrypted. The payload is only base64url text, and anyone can decode it. The signature stops changes. It does not hide anything.

"JWT is stateless, so it is always better than sessions." The check is stateless, but cancelling is not. To block a user at once, you need state again: a refresh record or a denylist. Session lookups used 20% of one store at 20,000 requests per second, which is small.

"Logout deletes the token, so the user is logged out." Deleting the token on the phone removes only that copy. A copy that someone else holds stays valid until exp. To log out for real, delete the server record: the session, or the refresh token.

07How to say it in the interview

Say it like this

I keep a random session ID in a Secure, HttpOnly cookie and the record in a shared store, so a logout is one delete. With a signed 15-minute token and a stored refresh token, servers check logins without a lookup, and a stolen token works 15 minutes at most.

08Check your understanding

  1. What are the three parts of a JWT? Why must you put no secret in it? What does a server do to check one?
  2. Peak load is 30,000 requests per second. The store does 100,000 operations per second. A session ID is 32 bytes and a token is 300 bytes. (a) What share of the store do session lookups use? (b) How much more data per second do tokens add than session IDs, in megabits? (c) With 600,000 users and a 15-minute access token, how many refresh lookups per second?
  3. A team uses 24-hour tokens. After a hacked account is reported, support blocks the user, but the attacker still reads orders for hours. Why? What do you change?

09Answers

Show the answers
  1. Header, payload and signature. The payload is only encoded, so anyone can read it. The server makes the signature again with its secret key, compares it, and checks that the time is before exp.
  2. (a) 30,000 / 100,000 = 30%. (b) Tokens: 30,000 x 300 = 9,000,000 bytes per second = 72 megabits. Session IDs: 30,000 x 32 = 960,000 bytes per second = 7.7 megabits. The difference is about 64.3 megabits per second. (c) 15 minutes = 900 seconds. 600,000 / 900 = 667 lookups per second.
  3. A token is valid until exp, and the servers do not ask anyone, so blocking the user changes nothing for 24 hours. Use an access token of about 15 minutes and a stored refresh token. Support deletes the refresh record. A denylist of token IDs also works. The window falls from 24 hours to 15 minutes, which is 96 times less.

10Sources

Try it now, no sign-up

More practice, in the app (free account)

Ask the AI tutor about this lesson

The tutor has read this lesson. Ask in your own words, any time. Free account, 20 questions a day.

Reading never needs an account. The account is for the tutor, the quiz, your notes and your progress.

All lessons in Module 16: Security and privacy