Session Security

Yo Yogesh.M Updated 17 Oct 2026
5 min read ·Lesson 73 of 109
In this post

Why Sessions Are a Prime Target In simple terms: After a user logs in, the session ID stored in a cookie is the only thing that proves "this request comes from Asha". Whoever has that ID is Asha as fa

Why Sessions Are a Prime Target

In simple terms: After a user logs in, the session ID stored in a cookie is the only thing that proves "this request comes from Asha". Whoever has that ID is Asha as far as the application is concerned — no password needed. Session security is about keeping session IDs secret, unguessable, short-lived, and impossible to plant.

Technically: PHP sessions identify users by a random ID sent in a cookie (see $_SESSION and PHP Sessions). Attacks target that ID: stealing it (session hijacking), forcing a known ID on a victim (session fixation), or abusing an open session left behind (unattended computers, long-lived sessions). Defenses combine secure cookie settings, ID regeneration, timeouts, proper logout, and protecting the session storage.

Threats and Defenses

ThreatHow It HappensDefense
Hijacking via network sniffingThe cookie travels over unencrypted HTTPHTTPS everywhere; Secure cookie flag; HSTS
Hijacking via XSSA script reads document.cookieHttpOnly cookie flag; XSS prevention
FixationThe attacker makes the victim use a session ID the attacker knowsuse_strict_mode; regenerate the ID at login
PredictionGuessable IDsPHP's default random IDs (do not generate your own)
CSRFOther sites use the victim's sessionCSRF tokens; SameSite cookies (see CSRF Protection)
Abandoned sessionsShared or stolen devicesIdle and absolute timeouts; logout; re-authentication
Storage exposureSession files readable by other users or appsPrivate save path or protected Redis/database

Secure Session Configuration

<?php
function startSecureSession(): void
{
    if (session_status() === PHP_SESSION_ACTIVE) {
        return;
    }

    session_start([
        'name' => '__Host-SID',
        'cookie_path' => '/',
        'cookie_secure' => true,
        'cookie_httponly' => true,
        'cookie_samesite' => 'Lax',
        'use_strict_mode' => true,
        'use_only_cookies' => true,
        'gc_maxlifetime' => 7200,
    ]);
}
SettingProtection
name => '__Host-SID'The __Host- prefix makes browsers accept the cookie only if it is Secure, has Path=/, and no Domain — subdomains cannot set or overwrite it (requires HTTPS; use a plain name such as SID on local http:// setups)
cookie_secureThe cookie is only sent over HTTPS
cookie_httponlyJavaScript cannot read the cookie, so XSS cannot steal it directly
cookie_samesite => 'Lax'The cookie is not sent with cross-site POST requests
use_strict_modePHP rejects session IDs it did not create and issues a new one — blocks most fixation attacks
use_only_cookiesSession IDs in URLs are ignored (they leak via links, logs, and Referer headers)

The same values can be set in php.ini (session.cookie_secure = 1, …) so that every script is protected.


Regenerating the Session ID

The Fixation Attack

1. Attacker obtains a valid session ID (e.g., visits the site: SID=abc123)
2. Attacker tricks the victim into using SID=abc123 (a crafted link or a cookie set via a vulnerable subdomain)
3. Victim logs in — the session abc123 is now authenticated
4. Attacker uses SID=abc123 and is logged in as the victim

The Fix: New ID at Every Privilege Change

<?php
session_start();

$before = session_id();
session_regenerate_id(true);

var_dump($before === session_id());
var_dump(strlen(session_id()));

Output

bool(false)
int(32)

session_regenerate_id(true) gives the session a new ID and deletes the old session data from storage (true), so an ID known to an attacker becomes worthless. Call it:

  • immediately after a successful login,
  • when privileges change (for example, entering an admin area or after re-authentication),
  • periodically during long sessions (see PHP Sessions).

A Login and Logout Pair

<?php
function logIn(int $userId): void
{
    session_regenerate_id(true);

    $_SESSION['user_id'] = $userId;
    $_SESSION['logged_in_at'] = time();
    $_SESSION['last_activity'] = time();
    $_SESSION['_csrf'] = bin2hex(random_bytes(32));   // fresh CSRF token
}

function logOut(): void
{
    $_SESSION = [];

    $params = session_get_cookie_params();
    setcookie(session_name(), '', [
        'expires' => time() - 3600,
        'path' => $params['path'],
        'domain' => $params['domain'],
        'secure' => $params['secure'],
        'httponly' => $params['httponly'],
        'samesite' => $params['samesite'],
    ]);

    session_destroy();
}

A complete logout clears the session data in memory, deletes the cookie in the browser (with the same parameters it was set with), and destroys the stored session. Logging out should be a POST request protected by a CSRF token.


Re-Authentication for Sensitive Actions

Even with a valid session, sensitive actions — changing the email or password, viewing payment details, deleting the account — should require proof that the user is present, not just their browser:

<?php
function passwordRecentlyConfirmed(array $session, int $now, int $maxAgeSeconds = 900): bool
{
    $confirmedAt = $session['password_confirmed_at'] ?? null;

    return is_int($confirmedAt) && $now - $confirmedAt <= $maxAgeSeconds;
}

$now = 1_792_054_800;

var_dump(passwordRecentlyConfirmed(['password_confirmed_at' => $now - 300], $now));
var_dump(passwordRecentlyConfirmed(['password_confirmed_at' => $now - 3600], $now));
var_dump(passwordRecentlyConfirmed([], $now));

Output

bool(true)
bool(false)
bool(false)

If the password was not confirmed within the last 15 minutes, the application redirects to a "confirm your password" page, verifies the password with password_verify(), stores password_confirmed_at, and regenerates the session ID. GitHub and many banking sites use this pattern ("sudo mode").


Additional Measures

MeasureNotes
Idle and absolute timeoutsExpire inactive sessions (e.g., 30 minutes) and all sessions after a maximum age (e.g., 8 hours); see PHP Sessions
Minimal session dataStore the user ID and small flags; load the user's data from the database on each request so that role changes and account locks take effect immediately
Server-side session listWith database-stored sessions, users can see active devices and log out everywhere; invalidate all sessions after a password change
Protected storageFile sessions in a directory readable only by the PHP user (not a shared /tmp on shared hosting); Redis with authentication and no public network access
Binding to client detailsComparing the stored User-Agent with each request can detect crude hijacking, but it is easy to spoof and IP addresses change legitimately (mobile networks); treat it as a weak signal only

Common Mistakes

Not Regenerating the ID at Login

Effect: Session fixation attacks succeed.

Fix: Call session_regenerate_id(true) after authentication.

Serving Pages Over HTTP

Effect: Session cookies can be intercepted on public networks.

Fix: Use HTTPS for the entire site and set the Secure flag.

Incomplete Logout

Effect: The old session remains valid on the server.

Fix: Clear data, delete the cookie, and call session_destroy().

Storing Permissions Only in the Session

Effect: Revoked roles or locked accounts stay active until the session ends.

Fix: Re-check the user's status and roles from the database.

Very Long Sessions Without Re-Authentication

Effect: Stolen or abandoned sessions remain useful for a long time.

Fix: Use timeouts and require the password for sensitive actions.


Best Practices

  • Configure sessions with Secure, HttpOnly, SameSite, strict mode, and cookie-only IDs.
  • Regenerate the session ID on login and privilege changes.
  • Implement complete logout and invalidate sessions after password changes.
  • Enforce idle and absolute timeouts and re-authenticate for sensitive actions.
  • Protect the session storage and keep session data minimal.

Official reference: https://www.php.net/manual/en/features.session.security.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators
Ask about this post
AI Ask about this post

Ask questions about Session Security and get answers drawn from it.

Signed-in readers only.