Intermediate 4 min · March 06, 2026

PHP Sessions - No session_regenerate_id Opens Hijack

Users saw others' order histories because session_regenerate_id was skipped.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
Quick Answer
  • Cookies store data in the browser — user can read and edit them.
  • PHP sessions keep data on the server; browser holds only an opaque ID.
  • session_start() and setcookie() must run before any HTML output.
  • Call session_regenerate_id(true) after login to block session fixation.
  • For "Remember Me", store a hashed token in DB, put raw token in a cookie.
  • Sessions add disk/DB load per request; cookies cost the server nothing.

Every time you log into a website, add something to a shopping cart, or see your name in the top-right corner of a page, something has to remember who you are. HTTP — the protocol the web runs on — is completely stateless. Every request is a stranger walking in off the street. Without a mechanism to bridge those requests, your login would vanish the moment you clicked to the next page. That's not a quirk — it's a fundamental architectural problem that every web application must solve.

Sessions and cookies are the two tools PHP gives you to solve it. They work together, but they're not interchangeable. Get the choice wrong and you'll either leak sensitive data or struggle with performance. Here's the real difference: cookies hand the data to the browser, sessions keep it on your server. That one decision drives everything else.

Cookies — Storing Small Bits of Data in the User's Browser

A cookie is a tiny piece of text your server sends to the browser, which the browser then sends back on every subsequent request to that domain. Think of it as a sticky note you hand to your visitor and ask them to bring back every time they knock on your door.

Cookies are set with setcookie() in PHP — and here's the critical detail that trips everyone up: you must call setcookie() before any HTML output reaches the browser, because cookies are sent as HTTP headers. Once the body starts streaming, headers are locked.

Each cookie has a name, a value, and an expiry time. The expiry is a Unix timestamp — pass 0 and the cookie dies when the browser closes (a 'session cookie' in browser terminology, not to be confused with a PHP session). Pass time() + 86400 and it survives for exactly one day.

Cookies are best for lightweight, non-sensitive preferences: theme choice, language, a 'remember me' token that points to server-side data. Never store a password, a credit card number, or a user ID in a raw cookie — the user can read and edit every cookie in their browser's dev tools.

PHP Sessions — Keeping Sensitive State on the Server

A PHP session stores data on the server and gives the browser a single, random session ID (by default stored in a cookie named PHPSESSID). The browser presents that ID on each request, and PHP uses it to look up the right data file on disk. The user sees only an opaque random string — not your actual data.

This is fundamentally more secure than cookies for anything sensitive, because the data never travels over the wire. An attacker who intercepts a session ID can hijack a session, but they can't read or forge the underlying data just from the ID alone.

Start a session with session_start() — again, before any output. Then read and write to the $_SESSION superglobal like a regular array. PHP handles serialisation, file locking, and garbage collection for you.

Sessions have a default lifetime tied to when the browser closes, but you can extend this by adjusting session.gc_maxlifetime in php.ini, or by updating a last-activity timestamp in $_SESSION yourself and expiring it manually — which gives you much more precise control than relying on the garbage collector.

Sessions vs Cookies — Choosing the Right Tool for the Job

Now that you've seen both in action, let's talk about the decision you'll make constantly as a PHP developer: which one do I reach for?

The rule of thumb is deceptively simple: if it's sensitive or needs to be trustworthy, it goes in the session. If it's a low-stakes preference and you want it to outlive a browser restart, a cookie is fine.

Where it gets interesting is 'remember me' functionality. You don't actually store login state in a cookie. Instead, you generate a cryptographically random token, store it hashed in your database linked to the user, put the raw token in a long-lived cookie, and when that cookie is presented, you look up the hash, verify it, and silently start a new session. This way the cookie is useless to an attacker without the database.

Performance is another consideration. Sessions read from disk (or a cache layer like Redis in production) on every request. For very high-traffic applications, storing session data in Redis with session_set_save_handler() or a PHP session handler extension is standard practice. Cookies, being client-side, add zero server load — which is why JWTs have become popular for stateless APIs, though that's a topic for another day.

Session Security — Preventing Hijacking and Fixation

Sessions are secure by design — data stays on the server. But the session ID itself is a key that can be stolen or forged. Here are the three controls that matter in production:

  1. Regenerate the session ID on privilege changes — you already know this from the fixation warning. But also regenerate on role changes, password changes, and any escalation.
  2. Bind the session to the user's browser fingerprint — store a hash of the User-Agent and/or a subset of the IP address in the session. If the fingerprint changes mid-session, destroy the session and force re-login. This blocks session hijacking after ID theft.
  3. Set session cookie flags — HttpOnly (prevents JS access), Secure (only over HTTPS), SameSite (Lax or Strict to stop CSRF). These are not set by default in all PHP versions — you must configure them explicitly.

Here's a practical setup that hardens sessions for most applications:

Scaling Sessions — Beyond File-Based Storage

By default, PHP stores session data in files on the server's filesystem. This works fine for a single server, but falls apart as soon as you add a second web server behind a load balancer. User A's session data lives on Server 1; the next request hits Server 2, and PHP can't find the session file. The user gets logged out.

The industry standard solution is a shared session storage backend. Redis is the most popular choice for PHP. It's fast, in-memory, and supports automatic expiry. A Redis session handler uses session_set_save_handler() or a PHP extension like redis.

Here's a practical setup using the predis/predis library (or the native redis extension) to store sessions in Redis:

PHP Sessions vs Cookies — Quick Reference
Feature / AspectPHP SessionsCookies
Where data livesServer (disk or cache)User's browser
Data size limitEffectively unlimited~4KB per cookie
SecurityHigh — user sees only an IDLow — user can read and edit values
Survives browser close?No (by default)Yes (if expiry is set)
Works without JavaScript?YesYes
Adds server load?Yes — disk/DB read per requestNo — zero server cost to read
Best forLogin state, cart contents, sensitive flagsTheme, language, remember-me tokens
Accessible via JavaScript?No (not directly)Only if httponly is false
Controlled by server?YesPartially — browser can reject or expire
GDPR / consent required?Session cookies: often exemptPersistent cookies: yes, consent needed

Key Takeaways

  • Cookies live in the browser — the user owns them and can edit them. Never store anything that grants access or trust in a raw cookie value.
  • PHP sessions store data on the server; the browser only holds an opaque session ID. Call session_regenerate_id(true) immediately after login to block session fixation.
  • Both setcookie() and session_start() send HTTP headers — they must be called before a single byte of output, or you'll get the 'headers already sent' error every time.
  • A 'remember me' feature is not a session and not a login cookie — it's a server-side token lookup: generate a random token, hash it in the DB, put the raw token in a long-lived cookie, and verify the hash on each visit.
  • In a load-balanced environment, file-based sessions break. Switch to a shared Redis or database session handler.

Common Mistakes to Avoid

  • Calling session_start() or setcookie() after output has begun
    Symptom: "Warning: Cannot modify header information — headers already sent by (output started at index.php:1)". The session or cookie is silently not set.
    Fix: Move session_start() and all setcookie() calls to the absolute top of every PHP file, before any echo, HTML, or even a blank line outside the <?php tag. Use output buffering (ob_start()) as a last resort in legacy code.
  • Skipping session_regenerate_id(true) after login
    Symptom: No visible error, but the application is silently vulnerable to session fixation attacks where an attacker pre-sets a session ID and inherits the authenticated session.
    Fix: Always call session_regenerate_id(true) immediately after verifying login credentials. The 'true' argument deletes the old session file; without it, the old ID remains valid.
  • Storing sensitive data directly in cookies
    Symptom: User opens browser DevTools > Application > Cookies and sees their user_id, role, or email in plain text, which they can edit to escalate privileges.
    Fix: Store only opaque, meaningless tokens in cookies. Keep real data in $_SESSION or the database, and always call htmlspecialchars() before rendering any cookie value into HTML to prevent XSS.
  • Using file sessions in a load-balanced environment
    Symptom: Users repeatedly logged out or their cart appearing empty on different requests.
    Fix: Switch to a shared session handler: Redis, Memcached, or a database. Use session_set_save_handler() or configure the native redis extension.

Interview Questions on This Topic

  • QWhat is the difference between a session and a cookie in PHP, and how do they work together under the hood?JuniorReveal
    A cookie is a small text file stored in the user's browser, sent with every request to the same domain. A PHP session stores data on the server and gives the browser only a session ID (usually in a cookie named PHPSESSID). The ID is used by PHP to look up the session data on the server. Cookies are suitable for non-sensitive preferences because the user can inspect and modify them. Sessions are for sensitive data like login status because the actual data never leaves the server.
  • QWhat is a session fixation attack, and what single line of PHP code is the primary defence against it?Mid-levelReveal
    A session fixation attack occurs when an attacker forces a known session ID onto a victim's browser (e.g., by sending a link with ?PHPSESSID=attacker_id). Once the victim logs in, the attacker uses the same ID to access the authenticated session. The primary defence is session_regenerate_id(true) called immediately after successful login. The true parameter deletes the old session file.
  • QIf a user ticks 'Remember Me' on your login form, how would you implement that securely — and why is storing the user's ID in a cookie the wrong approach?SeniorReveal
    Storing the user ID directly in a cookie is insecure because the user can modify it (e.g., change the ID to another user's ID) and gain unauthorised access. Secure implementation: after login, generate a cryptographically random token (e.g., bin2hex(random_bytes(32))), store its SHA-256 hash in a database linked to the user ID with an expiry date, and set the raw token in a long-lived cookie (httponly, secure, samesite=Lax). On subsequent requests, read the cookie, hash it, look up the hash in the DB, and if valid, silently start a new session. This way, even if the cookie is stolen, the attacker cannot reverse the hash to obtain the original token, and token rotation invalidates old cookies.

Frequently Asked Questions

How long does a PHP session last by default?

By default, a PHP session lasts until the browser is closed, because the PHPSESSID cookie has no expiry set. On the server side, the session data file is eligible for garbage collection after session.gc_maxlifetime seconds (default 1440 — 24 minutes of inactivity). You can extend this in php.ini or by implementing your own timeout logic using a timestamp stored in $_SESSION.

Can I use PHP sessions without cookies?

Yes — PHP can pass the session ID in the URL as a query parameter (e.g. page.php?PHPSESSID=abc123) if you set session.use_trans_sid = 1 in php.ini. However, this is a serious security risk because session IDs appear in browser history, server logs, and Referer headers. Stick with cookie-based sessions and set session.use_only_cookies = 1 to enforce it.

What is the difference between session_unset() and session_destroy()?

session_unset() clears all variables stored in $_SESSION for the current session, but the session itself (and its server-side file) still exists. session_destroy() deletes the session file on disk but does NOT clear the $_SESSION superglobal in the current request. For a proper logout, you should do both: set $_SESSION = [] to clear variables, then call session_destroy() to remove the file, and finally expire the PHPSESSID cookie in the browser.

How do I make sessions work across multiple servers behind a load balancer?

You cannot use the default file-based session handler across multiple servers. Instead, configure a shared session storage backend like Redis, Memcached, or a database. In php.ini set session.save_handler = redis and session.save_path = 'tcp://redis-host:6379'. Alternatively, implement a custom handler using session_set_save_handler() that stores session data in a shared MySQL/MariaDB table.

What is SameSite cookie attribute and why should I use it?

The SameSite attribute (Strict, Lax, or None) controls whether a cookie is sent with cross-site requests. Setting SameSite=Lax prevents the browser from sending the session cookie with POST requests from other sites, which is a strong defence against CSRF attacks. SameSite=Strict offers even more protection but may break some legitimate cross-site navigation. Use Lax for session cookies.

🔥

That's PHP Basics. Mark it forged?

4 min read · try the examples if you haven't

Previous
PHP Forms and User Input
9 / 14 · PHP Basics
Next
PHP File Handling