Lesson 02 / 15
Sessions
The session mechanism that keeps state on the server: the ID in the session cookie, $_SESSION, session_regenerate_id(true), and the limit set by the party that carries the ID.
Published Updated
In this lesson
- Explains that the session cookie carries only an ID while the state stays on the server
- Keeps the cart on the server with session_start() and $_SESSION and measures that the client's cookie does not change the result
- Renews the ID at login with session_regenerate_id(true) and shows that the session data is preserved
- Measures the limit where the client can choose the ID and reads the sentence that classifies the session as server together with that limit
Contents
The Cookies lesson left the cart on the client, and the client could turn it from 2 into 5. The signature noticed the change but did not change the owner of the result. This lesson moves the same value to the server: the client keeps only an ID, and the cart itself stays on the server.
The mechanism is called a session. The session_start() call does two things. If the request carries a cookie with a session ID, the server opens the record that belongs to that ID and loads its contents into the $_SESSION array. If not, it generates a new ID, opens an empty record and sends the ID in the response as a cookie, the session cookie. The client returns this cookie on later requests. What the cookie carries is not the cart but the name of the record.
The ID is a handle. In the Memory and Pointers in C course, a pointer points at data that lives elsewhere, and the author of the code manages its lifetime. The session ID is likewise a handle to state on the server, except that the party holding it is the client, not the author of the code. The end of the lesson shows what that means for the measurement.
The Cart on the Server
The measurement uses the same core as the Cookies lesson; party.php is repeated here unchanged.
<?php // party.php — who can change this result? declare(strict_types=1); // The parties outside the code, in decision order: every request, every external call, every deployment. const EXTERNAL_PARTIES = ['client', 'third party', 'environment']; /** Computes the result once without deviation, then once with each external party's deviation. */ function probe(callable $result, array $deviations): array { $baseline = $result([]); $changed = []; foreach (EXTERNAL_PARTIES as $party) { if (array_key_exists($party, $deviations)) { $changed[$party] = $result([$party => $deviations[$party]]) !== $baseline; } } return $changed; } /** The first external party that can change the result; the server if none can. */ function party(array $changed): string { foreach (EXTERNAL_PARTIES as $p) { if ($changed[$p] ?? false) return $p; } return 'server'; } function partyReport(string $name, callable $result, array $deviations): string { $changed = probe($result, $deviations); $lines = []; foreach ($changed as $p => $c) $lines[] = $p . ($c ? ' changed' : ' could not change'); $owner = party($changed); echo "$name → $owner\n ", implode(' · ', $lines), "\n"; return $owner; }
The new file is session.php. It first sets up the session with four explicit settings, because the lesson does not trust the runtime default. session.use_strict_mode asks the server not to accept an ID it does not recognize; its default is off. session.cookie_samesite restricts sending the session cookie with another site’s request; its default is empty. session.cookie_httponly hides the cookie from scripts on the page. session.save_path ties the record location to the temporary directory. ob_start() buffers the output, because session headers must be sent before the first output.
<?php // session.php — the cart lives in the session; the client's cookie cannot change the result declare(strict_types=1); require 'party.php'; ob_start(); ini_set('session.use_strict_mode', '1'); ini_set('session.cookie_samesite', 'Lax'); ini_set('session.cookie_httponly', '1'); ini_set('session.save_path', sys_get_temp_dir()); session_start(); $_SESSION['cart'] = 2; $previous = session_id(); session_regenerate_id(true); echo 'id regenerated: ', session_id() !== $previous ? 'different' : 'same', "\n"; echo 'cart preserved: ', $_SESSION['cart'], "\n"; partyReport( 'cart (in session)', function (array $d): int { if (isset($d['client'])) { $_COOKIE['cart'] = $d['client']; } return $_SESSION['cart']; }, ['client' => '5'], ); ob_end_flush();
id regenerated: different
cart preserved: 2
cart (in session) → server
client could not change
The first two output lines are the ID renewal done at login; the last two are the measurement. The client sends cart=5, and the deviation writes it into $_COOKIE, but the code that reads the cart looks at $_SESSION['cart']. The cookie changes, the result does not.
SS5. The decision order is client, third party, environment, server. The counterpart is the first line of the Cookies lesson: there the same deviation turned the cart from 2 into 5, and the result was client. Here the same deviation could not change the result, which is server. The difference is where the value is carried: in the cookie, the sender owned the cart; once the cart sits in the server record, the sender keeps only the name of the record.
The Record’s Lifespan and Cost
Staying on the server has two costs. The first is lifespan. The record has to be deleted when the user logs out or when it expires; otherwise unused records pile up. Once it is deleted, the ID in the client’s hand is invalid, and while session.use_strict_mode is on, the server rejects it and generates a new ID. The ID on the client does not guarantee that the record lives; the server decides that.
The second is scaling. If records sit in one machine’s temporary directory, a second request of the same user landing on another machine cannot find the record. An application running on several machines therefore writes records to a shared store. This is a configuration decision and leads into the next lesson: the environment, not the code, should choose the store.
Staying on the server also means the correctness of the cart no longer depends on the honesty of the client. A cart arriving as 5 in a cookie would have to be verified; in a session there is nothing to verify, because the server wrote the value. The burden moves: the validity of the ID reduces to whether the record exists, and session_start() checks that on its own.
Renewing the ID
If the ID stays the same before and after login, anyone who knows it also knows the logged-in session. This is called session fixation. The remedy is to call session_regenerate_id(true) at the moment the user is authenticated. The function generates a new ID, the true argument deletes the old record, and the $_SESSION contents move to the new one.
The measurement shows this in two lines: after the renewal the ID is different, and cart is preserved as 2. The ID itself is not printed, because it changes on every run. The fixation attack is not built here; the mechanism only removes the assumption it relies on, that the ID stays the same across login.
SS6. session_regenerate_id(true) secures the server’s own record and does not change the party sentence: the party that generates the ID, renews it and deletes the old record is the server. But the ID still travels to the client as a cookie, and the next section covers that.
The Bounding Measurement
The session was classified as server, but the session ID itself is on the client. What happens if the client presents a different ID? The file below opens a session, sets the cart to 2, then reads the cart with two IDs: the one the server issued and a different one the client presents. The different ID is a neutral string the server never generated.
<?php // other.php — which session the client lands in if it presents a different session ID declare(strict_types=1); require 'party.php'; ob_start(); ini_set('session.use_strict_mode', '1'); ini_set('session.cookie_samesite', 'Lax'); ini_set('session.cookie_httponly', '1'); ini_set('session.save_path', sys_get_temp_dir()); function readCart(string $sessionId): int { session_id($sessionId); session_start(); $cart = $_SESSION['cart'] ?? 0; session_write_close(); return $cart; } session_start(); $_SESSION['cart'] = 2; $sessionId = session_id(); session_write_close(); partyReport( 'cart (in session, client presents another ID)', fn(array $d) => readCart($d['client'] ?? $sessionId), ['client' => 'otheridentifier0123456789'], ); ob_end_flush();
cart (in session, client presents another ID) → client
client changed
SS7. The result changed, and the party that changed it is the client. The server rejected the unrecognized ID (session.use_strict_mode) and opened a new, empty record; the cart read 0 instead of 2. The client landed not in its own cart but in a session with no cart at all.
A second reading: the deviation did not change the server’s record, which stayed 2; what changed is which session was opened. One measurement thus yields two sentences: the client cannot determine the contents of the cart, but it determines which cart is opened.
Summary
- A session keeps state on the server. All that remains on the client is an ID, and the session cookie carries it back with every request.
session_start()opens the record and loads it into$_SESSIONif an ID exists; if not, it generates a new ID and an empty record.- The same
cart=5deviation changed the result in the Cookies lesson; in the session it could not. The measured party of this lesson is server: the client cannot determine the contents of the cart. session_regenerate_id(true)renews the ID at login, deletes the old record and preserves the$_SESSIONcontents; this is the mechanism against fixation.- The limit: the party that carries the ID is the client. If the client presents a different ID, it lands not in its own cart but in a different session, so which session is opened stays with the client.
Next Step
Both the cookie and the session measured data that arrives with the request. Not every value an application reads comes from the request: the database name, the key and the connection settings come from the deployment environment. The next lesson, Configuration and Environment Variables, leaves these values to the environment and measures who can change them.
To keep your progress and take notes, Log in
My notes
Log in to take notes.