Sessions and Tokens — From the Browser to the Mesh
Logged in, but the session ID never changed
In one line
A server session means the server holds the state and gives the browser only a key (the session ID). So you can invalidate it immediately with a single logout, but if that key does not change before and after login, session fixation becomes possible, in which someone rides onto my login using a key they planted in advance.
Why this was needed
When you build a login form, you usually write it like this. On a visit it creates a session and hands it down as a cookie, and when the login succeeds it fills that session with username. The problem is that the session ID is the same before and after login.
An attacker can plant a session ID they know in the victim's browser in advance (older servers accepted ;jsessionid= in the URL, or overwriting a cookie from a subdomain). If the victim logs in with that very ID, the server just puts the logged-in state on "the session that was already there". The attacker uses the logged-in session as is, with the ID they knew from the start. They end up holding another person's login session without knowing the password.
The way to block it is summed up in one sentence by the OWASP Session Management Cheat Sheet — "The session ID must be renewed or regenerated by the web application after any privilege level change." The moment you go from anonymous to authenticated is exactly that privilege change. When the login succeeds, issue a new ID and throw the old ID away. Then the ID the attacker planted becomes a dead key right after the login.
How it works
There are two reasons to put the session store in an external store such as Redis. First, when there are several web servers, whichever one the request goes to sees the same session (if you keep it in local memory, only the server you were attached to has the session). Second, because the server holds the state, you can erase it at any time to invalidate it immediately — this is the decisive advantage a server session has over a JWT.
A session ID must be unguessable. OWASP requires "at least 64 bits of entropy". In Python, secrets.token_urlsafe(32) gives 256 bits. The key in the store is sess:<id>, and the value holds at least the user, the creation time, and the last activity time.
Expiry has two kinds together. It is exactly the distinction made by OWASP.
idle timeout 마지막 활동 이후 N분간 조용하면 만료. 요청마다 갱신(sliding).
자리를 비운 세션이 영원히 살아 있지 않게 한다.
absolute timeout 세션을 만든 지 M시간이 지나면, 계속 쓰고 있었더라도 만료.
탈취된 세션이 무한정 연장되지 않게 하는 상한이다.
With Redis, idle is naturally expressed as the key's TTL — if you push the TTL out again with EXPIRE on every request, it becomes sliding. But TTL alone cannot express absolute (it gets pushed on every request, so it never dies). So you put the creation time separately inside the value, and on every request, if now - created > absolute, you reject it even if idle TTL remains.
We will look at cookie attributes in depth in the next module, but the minimum line for a session cookie is HttpOnly (so scripts cannot read it), Secure (HTTPS only), SameSite=Lax or stricter, and putting the __Host- prefix on the name so that a subdomain cannot overwrite it.
What it looks like in the field
Reports of "sometimes I get logged in to someone else's account" come in now and then, although rarely. Usually it is a case in code that does not regenerate the session ID, where someone rode onto another person's session through a shared PC or XSS on a subdomain. Even when you look at the logs, it is a normal login, so the cause does not show — the fact that the session ID is the same before and after login is revealed only by looking at the application code.
Conversely, there are moments when the advantage of a server session shines. When a report comes in that an account was compromised, just deleting that user's session keys from the store logs them out of every device immediately. With a JWT, you would have to wait until expiry or run a blocklist separately (covered in a later module).
In exchange, the session store becomes a single point of failure for the whole login. If Redis stops, it is the same as every user being logged out at once. So in production you set up replication and failover and write it so that, when the store cannot be reached, it does not fall toward the side of "just treat them as logged in" (fail-closed). Always setting a TTL on every key is for the same reason — session keys without a TTL pile up in as many numbers as users who left without logging out and eat memory, and someday the eviction policy starts deleting even live sessions.
What you will do in the next lab
You stand up a login server with Redis attached as the session store, check the cookie attributes, and confirm with real requests whether the session ID is regenerated at the moment of login. You try logging in with a session ID planted in advance and even see that ID become invalid right after login. Finally, you confirm that a session past its absolute expiry is rejected regardless of the idle TTL.