Sessions & Cookies
How stateless HTTP remembers a logged-in user: cookies on the browser, sessions on the server, the security flags that protect them, session storage when you have many servers, and how Spring Boot and React handle the same problem.
Running examples: guest cart that survives login, banking idle timeout, remember-me
1. Cookies and sessions explained
HTTP is stateless: the server forgets you after each response. A cookie is a small name=value pair the server asks the browser to store (Set-Cookie header); the browser sends it back on every later request to that site (Cookie header).
A session keeps the data on the server and puts only a random id in the cookie. The browser holds laravel_session=Abc; the server holds Abc => {user_id: 42, cart_id: 9}. Users cannot read or edit server-side data, and large data never travels with each request.
What travels over the wire
POST /login HTTP/1.1 (credentials checked)
HTTP/1.1 302 Found
Set-Cookie: laravel_session=eyJpdiI6...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=7200
GET /orders HTTP/1.1
Cookie: laravel_session=eyJpdiI6... (browser sends it automatically)
| Flag | Effect | Protects against |
|---|---|---|
| Secure | Sent only over HTTPS | Theft on public Wi-Fi |
| HttpOnly | JavaScript cannot read it | XSS stealing the session |
| SameSite=Lax/Strict | Not sent on cross-site POSTs (Strict: no cross-site at all) | CSRF |
| Max-Age / Expires | When the browser deletes it | Sessions living forever on shared PCs |
| Domain / Path | Which hosts and paths receive it | Leaking to other subdomains |
2. Sessions in Laravel
Guest cart merged at login, then session id regenerated
// Adding to cart as a guest
$cart = session('cart', []);
$cart[$product->id] = ($cart[$product->id] ?? 0) + 1;
session(['cart' => $cart]);
// After successful login
public function authenticated(Request $request, User $user)
{
foreach (session()->pull('cart', []) as $productId => $qty) {
$user->cartItems()->updateOrCreate(['product_id' => $productId], ['quantity' => DB::raw("quantity + {$qty}")]);
}
$request->session()->regenerate(); // new id: blocks session fixation attacks
}
// Logout
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken(); // new CSRF token
Banking settings in config/session.php (via .env)
SESSION_DRIVER=redis # shared by every app server
SESSION_LIFETIME=10 # minutes idle before logout
SESSION_EXPIRE_ON_CLOSE=true
SESSION_SECURE_COOKIE=true
SESSION_ENCRYPT=true
Flash data (session()->flash("status", "Order placed")) lives for exactly one more request, which is how "saved successfully" messages survive a redirect. Remember-me uses a separate long-lived cookie holding a token that is checked against users.remember_token; turn it off for banking.
3. Sessions with many servers
With the default file driver each server keeps its own session files, so a load balancer that sends the next request to another server logs the user out. Fix it with a shared store (Redis, database, Memcached) or with sticky sessions. Shared storage is preferred because sticky sessions break when a server is removed during a deployment.
| Driver | Speed | Multi-server | Notes |
|---|---|---|---|
| file | Good | No | Default; fine for one server or shared hosting |
| cookie | Good | Yes | Whole session in an encrypted cookie, 4 KB max, cannot be revoked server-side |
| database | OK | Yes | Easy to list and delete a user's sessions ("log out all devices") |
| redis | Excellent | Yes | Usual production choice |
Spring Boot: the same with Spring Session + Redis
// build.gradle: implementation 'org.springframework.session:spring-session-data-redis'
// application.yml
// spring.session.store-type: redis
// server.servlet.session.timeout: 10m
// server.servlet.session.cookie: { http-only: true, secure: true, same-site: strict }
@GetMapping("/cart")
public Cart cart(HttpSession session) {
return (Cart) Optional.ofNullable(session.getAttribute("cart")).orElseGet(Cart::new);
}
4. React SPA: cookies vs storage
A React app on the same site as the Laravel API can simply use the session cookie (Sanctum SPA mode): call /sanctum/csrf-cookie once, then log in, and every fetch with credentials: "include" is authenticated. This is safer than tokens in localStorage because the cookie is HttpOnly.
Sanctum SPA login from React
await fetch('/sanctum/csrf-cookie', { credentials: 'include' }); // sets XSRF-TOKEN cookie
await fetch('/login', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json', 'X-XSRF-TOKEN': decodeURIComponent(getCookie('XSRF-TOKEN')) },
body: JSON.stringify({ email, password }),
});
const me = await (await fetch('/api/user', { credentials: 'include' })).json();
| Data | Where |
|---|---|
| Session / auth | HttpOnly cookie set by the server |
| Theme, language | localStorage |
| Form draft | sessionStorage or React state |
| Card number, password | Nowhere on the client; send once over HTTPS |
5. Interview questions
Session vs cookie?
A cookie is stored in the browser; a session is stored on the server and identified by a cookie. Cookies can be read or edited by the user unless encrypted/signed; sessions cannot.
What is session fixation?
An attacker gets the victim to use a session id the attacker knows, then waits for the login. Regenerating the id at login defeats it.
Why is the user logged out randomly behind a load balancer?
File sessions on separate servers; move to Redis or database sessions.
How do you implement "log out from all devices"?
Use database/Redis sessions with user_id and delete them, delete all API tokens, and rotate remember_token.
