# 🔧 Fix 419 Page Expired Error (CSRF Token Mismatch)

**Problem:** Getting "419 Page Expired" error on live server during checkout, but works fine on localhost.

**Cause:** CSRF token mismatch - usually session/cookie configuration issues on HTTPS.

**Last Updated:** 2025-11-19

---

## 🎯 Quick Fix (90% of Cases)

### **On Your Live Server .env File:**

Add/update these lines:

```env
# Session Configuration for HTTPS
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_SECURE_COOKIE=true
SESSION_DOMAIN=.your-domain.com
SESSION_SAME_SITE=lax

# App URL must match
APP_URL=https://your-domain.com

# Must be production
APP_ENV=production
APP_DEBUG=false
```

**Then run:**
```bash
php artisan config:clear
php artisan cache:clear
php artisan view:clear
```

**Test again - 90% of issues are fixed!**

---

## 🔍 Root Causes & Solutions

### **Cause 1: HTTPS Cookie Mismatch**

**Problem:** Live server uses HTTPS, but cookies are not configured for secure connection.

**Symptoms:**
- Works on localhost (HTTP)
- Fails on live server (HTTPS)
- Form submissions fail with 419

**Solution:**

Edit `.env` on live server:
```env
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
```

Clear cache:
```bash
php artisan config:clear
```

---

### **Cause 2: SESSION_DOMAIN Not Set**

**Problem:** Cookie domain doesn't match your actual domain.

**Symptoms:**
- 419 on form submission
- Cookies not being saved
- Session lost between requests

**Solution:**

**If your domain is:** `example.com`

```env
SESSION_DOMAIN=.example.com
```

**If your domain is:** `shop.example.com` (subdomain)

```env
SESSION_DOMAIN=.example.com
```

**Note:** The leading dot (`.`) allows cookies for all subdomains.

**Or for single domain only:**
```env
SESSION_DOMAIN=example.com
```

---

### **Cause 3: APP_URL Mismatch**

**Problem:** APP_URL doesn't match actual domain or protocol.

**Symptoms:**
- Mixed content warnings
- CSRF token mismatch
- Assets loading from wrong URL

**Solution:**

```env
# WRONG:
APP_URL=http://localhost
APP_URL=http://example.com  # Using HTTP on HTTPS site

# CORRECT:
APP_URL=https://example.com  # Match actual protocol!
```

**Then:**
```bash
php artisan config:clear
```

---

### **Cause 4: Session Table Missing**

**Problem:** Database session table doesn't exist.

**Check:**
```bash
php artisan tinker
>>> DB::table('sessions')->count();
```

**If error:**
```bash
# Run migrations
php artisan migrate --force

# Or create manually:
CREATE TABLE sessions (
    id VARCHAR(255) NOT NULL PRIMARY KEY,
    user_id BIGINT UNSIGNED NULL,
    ip_address VARCHAR(45) NULL,
    user_agent TEXT NULL,
    payload LONGTEXT NOT NULL,
    last_activity INT NOT NULL,
    INDEX sessions_user_id_index (user_id),
    INDEX sessions_last_activity_index (last_activity)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

---

### **Cause 5: Cached Configuration**

**Problem:** Old configuration cached.

**Solution:**
```bash
# Clear ALL caches
php artisan optimize:clear

# Or individually:
php artisan config:clear
php artisan cache:clear
php artisan view:clear
php artisan route:clear

# Then rebuild (optional):
php artisan config:cache
```

---

### **Cause 6: CSRF Token in Form Missing**

**Problem:** Form doesn't include CSRF token.

**Check your Blade template:**

```blade
<!-- CORRECT - Has @csrf -->
<form method="POST" action="/checkout">
    @csrf
    <button type="submit">Submit</button>
</form>

<!-- WRONG - Missing @csrf -->
<form method="POST" action="/checkout">
    <button type="submit">Submit</button>
</form>
```

**Or for AJAX:**
```javascript
// Add to headers
$.ajax({
    url: '/checkout',
    type: 'POST',
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    },
    data: formData,
    success: function(response) {
        // ...
    }
});
```

**Make sure layout has:**
```blade
<head>
    <meta name="csrf-token" content="{{ csrf_token() }}">
</head>
```

---

### **Cause 7: Load Balancer / Proxy**

**Problem:** Behind load balancer, IP address changes between requests.

**Solution:**

Add to `.env`:
```env
# Trust proxies
TRUSTED_PROXIES=*
# Or specific IPs:
TRUSTED_PROXIES=192.168.1.1,10.0.0.1
```

Edit `app/Http/Middleware/TrustProxies.php`:
```php
protected $proxies = '*'; // Trust all proxies
// Or:
protected $proxies = ['192.168.1.1'];
```

---

### **Cause 8: CDN / Cloudflare**

**Problem:** Using CDN, cookies not passing through.

**Solution:**

**Cloudflare Settings:**
1. Go to Cloudflare Dashboard
2. SSL/TLS → Full (strict)
3. Rules → Page Rules → Cache Level: Bypass for checkout pages

**In .env:**
```env
TRUSTED_PROXIES=*
SESSION_SECURE_COOKIE=true
```

---

### **Cause 9: CORS Issues**

**Problem:** API requests from different domain.

**Check:** `config/cors.php`

```php
'supports_credentials' => true,
'allowed_origins' => ['https://your-domain.com'],
```

---

### **Cause 10: Time Synchronization**

**Problem:** Server time is wrong, session expires immediately.

**Check:**
```bash
date
# Should show correct time
```

**Fix:**
```bash
# Install NTP
sudo apt install ntp
sudo systemctl start ntp
sudo systemctl enable ntp

# Or sync manually
sudo ntpdate pool.ntp.org
```

---

## 🛠️ Complete Fix Checklist

### **Step 1: Update .env on Live Server**

```env
# Application
APP_NAME=Omnia
APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-actual-domain.com

# Session (CRITICAL for HTTPS!)
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=.your-actual-domain.com
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax

# Cache
CACHE_STORE=database
```

---

### **Step 2: Clear All Caches**

```bash
ssh user@your-server.com
cd /var/www/your-domain

# Clear everything
php artisan optimize:clear

# Then cache config
php artisan config:cache
```

---

### **Step 3: Verify Session Table**

```bash
php artisan tinker
>>> DB::table('sessions')->count();
>>> exit

# Should return a number (0 or more)
# If error, run: php artisan migrate
```

---

### **Step 4: Test Cookies**

**Open browser DevTools (F12):**
1. Go to **Application** → **Cookies**
2. Visit your site
3. Check cookies exist:
   - `omnia-session` (or your app name)
   - Should have:
     - Domain: `.your-domain.com`
     - Secure: ✓ (checkmark)
     - HttpOnly: ✓ (checkmark)
     - SameSite: Lax

**If cookies not showing:**
- Check SESSION_DOMAIN in .env
- Check SESSION_SECURE_COOKIE=true
- Clear browser cookies manually

---

### **Step 5: Check HTTPS**

```bash
curl -I https://your-domain.com

# Should show:
# HTTP/2 200
# Strict-Transport-Security: max-age=...
```

**If showing errors:**
- Check SSL certificate
- Check APP_URL uses https://
- Check web server configuration

---

### **Step 6: Verify Forms Have CSRF**

Check your checkout form blade file:

```bash
grep -r "@csrf" resources/views/
```

**Should find in checkout forms:**
```blade
<form method="POST">
    @csrf
    <!-- form fields -->
</form>
```

---

### **Step 7: Check Middleware**

Verify CSRF middleware is in place:

```bash
cat app/Http/Kernel.php | grep VerifyCsrfToken
```

Should be in web middleware group.

---

### **Step 8: Test**

1. Clear browser cache (Ctrl+Shift+Delete)
2. Close all browser tabs
3. Reopen browser
4. Visit site
5. Try checkout again

---

## 🚨 Advanced Debugging

### **Enable Debug Mode Temporarily**

**In .env:**
```env
APP_DEBUG=true
LOG_LEVEL=debug
```

Try checkout again and check logs:
```bash
tail -f storage/logs/laravel.log
```

**IMPORTANT:** Set back to false after debugging!

---

### **Check Session Driver**

```bash
php artisan tinker
>>> config('session.driver');
# Should output: "database"

>>> config('session.secure');
# Should output: true (on HTTPS)

>>> config('session.domain');
# Should output: ".your-domain.com"
```

---

### **Check CSRF Exceptions**

Edit `app/Http/Middleware/VerifyCsrfToken.php`:

**Check if your route is accidentally excluded:**
```php
protected $except = [
    // These routes skip CSRF check
    'api/*',
];
```

**If checkout is here, remove it!**

---

### **Regenerate Session**

If session is corrupted:

```bash
# Clear sessions table
php artisan tinker
>>> DB::table('sessions')->truncate();
>>> exit
```

Then test again.

---

## 📊 Compare localhost vs Production

### **Localhost (.env):**
```env
APP_URL=http://localhost
SESSION_SECURE_COOKIE=  # Empty (HTTP works)
SESSION_DOMAIN=null
```

### **Production (.env):**
```env
APP_URL=https://your-domain.com  # HTTPS!
SESSION_SECURE_COOKIE=true       # Must be true!
SESSION_DOMAIN=.your-domain.com  # Must set!
```

**Key Differences:**
- HTTP vs HTTPS (protocol)
- Secure cookie requirement
- Domain specification

---

## ✅ Final Checklist

After making changes:

- [ ] `.env` updated with correct values
- [ ] `SESSION_SECURE_COOKIE=true` set
- [ ] `SESSION_DOMAIN` set to your domain
- [ ] `APP_URL` uses `https://`
- [ ] Config cache cleared (`php artisan config:clear`)
- [ ] Browser cache cleared
- [ ] Session table exists in database
- [ ] Forms have `@csrf` directive
- [ ] HTTPS/SSL working properly
- [ ] Cookies visible in DevTools
- [ ] Time sync correct on server
- [ ] Tested checkout - works! ✅

---

## 🎯 Quick Fix Template

**Copy this to your live server .env:**

```env
# ===================================
# FIX FOR 419 PAGE EXPIRED
# ===================================

# Replace with YOUR actual domain!
APP_URL=https://your-domain.com
SESSION_DOMAIN=.your-domain.com

# Required for HTTPS
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax
SESSION_DRIVER=database
SESSION_LIFETIME=120

# Required settings
APP_ENV=production
APP_DEBUG=false
```

**Then run:**
```bash
php artisan config:clear && php artisan config:cache
```

**Clear browser cache and test!**

---

## 📞 Still Not Working?

### **Check These:**

1. **Server logs:**
   ```bash
   tail -100 storage/logs/laravel.log
   tail -100 /var/log/apache2/error.log
   tail -100 /var/log/nginx/error.log
   ```

2. **Browser console (F12):**
   - Check for JavaScript errors
   - Check Network tab for failed requests
   - Check if CSRF token in headers

3. **Database connection:**
   ```bash
   php artisan tinker
   >>> DB::connection()->getPdo();
   ```

4. **Session write permissions:**
   ```bash
   ls -la storage/framework/sessions
   # Should be writable (775)
   ```

---

## 💡 Prevention for Future

**In .env.example, document the live server settings:**

```env
# LIVE SERVER SETTINGS (Update these for production!)
# APP_URL=https://your-live-domain.com
# SESSION_DOMAIN=.your-live-domain.com
# SESSION_SECURE_COOKIE=true
# APP_ENV=production
# APP_DEBUG=false
```

---

## 📚 Resources

- [Laravel CSRF Protection](https://laravel.com/docs/12.x/csrf)
- [Session Configuration](https://laravel.com/docs/12.x/session)
- [Cookie Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)

---

## ✅ Success Indicator

**After fixing, you should see:**

1. ✅ Checkout completes without 419 error
2. ✅ Session persists between requests
3. ✅ Cookies appear in DevTools with:
   - Secure: ✓
   - HttpOnly: ✓
   - Domain: .your-domain.com
4. ✅ No errors in browser console
5. ✅ No errors in Laravel logs

---

**Document Version:** 1.0
**Last Updated:** 2025-11-19
**Status:** Production Ready
