Un formulaire qui renvoie "Page Expired" juste après un clic sur Envoyer : c'est l'erreur 419. Laravel refuse la requête parce que le jeton CSRF qu'elle porte ne correspond pas à celui de la session de l'utilisateur. Avant de toucher aux réglages du serveur, regardez la requête qui a échoué dans l'onglet Réseau du navigateur, puis suivez cette liste.
Le formulaire envoie-t-il un jeton ?
Cause la plus fréquente : @csrf manque dans un formulaire POST, PUT, PATCH ou DELETE.
<form method="POST" action="{{ route('profile.update') }}"> @csrf @method('PUT') {{-- champs --}} </form>
@method ne change que la méthode HTTP, il n'apporte pas de jeton.
Et en AJAX, avec fetch ou axios ?
Placez le jeton dans le <head> de la mise en page, puis envoyez-le dans l'en-tête X-CSRF-TOKEN :
<meta name="csrf-token" content="{{ csrf_token() }}">
// resources/js/app.js await fetch('/commandes', { method: 'POST', credentials: 'same-origin', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, 'Content-Type': 'application/json', }, body: JSON.stringify({ ref: 'A-12' }), });
credentials: 'same-origin' envoie le cookie de session avec la requête. Avec axios, l'en-tête X-XSRF-TOKEN attend la valeur chiffrée du cookie XSRF-TOKEN, pas le jeton brut : en cas de doute, envoyez la valeur de la balise meta dans X-CSRF-TOKEN.
La session a-t-elle expiré ?
Par défaut, SESSION_LIFETIME vaut 120 minutes d'inactivité. Une page laissée ouverte une heure puis envoyée peut donc échouer. Se connecter ou se déconnecter dans un autre onglet change aussi le jeton : les formulaires restés ouverts ailleurs portent un jeton périmé. Dans ce cas, prévoyez une page d'erreur claire (resources/views/errors/419.blade.php) qui invite à recharger et à recommencer.
Le navigateur garde-t-il bien le cookie de session ?
Vérifiez ces réglages dans le .env :
APP_URL=https://exemple.fr SESSION_DRIVER=file SESSION_LIFETIME=120 SESSION_DOMAIN=null SESSION_SECURE_COOKIE=true
Trois pièges classiques : un SESSION_DOMAIN qui ne correspond pas au domaine, SESSION_SECURE_COOKIE=true sur un site en http:// (le cookie n'est jamais renvoyé), et un site accessible à la fois en www et sans www (deux cookies distincts : choisissez une adresse et redirigez l'autre). Avec SESSION_DRIVER=cookie, une session trop volumineuse dépasse la taille permise.
Le serveur peut-il enregistrer la session ?
- Pilote
file:storage/framework/sessionsdoit exister et être accessible en écriture. - Pilote
database: la tablesessionsdoit exister (php artisan migrate:status, ouphp artisan make:session-tablesi la migration manque). - Pilote
redis: l'extension PhpRedis oupredis/predisdoit être installée.
Un proxy ou un cache fausse-t-il la requête ?
Derrière un proxy ou un équilibreur de charge qui termine le HTTPS, Laravel doit lui faire confiance, sinon il croit être en HTTP :
// bootstrap/app.php ->withMiddleware(function (Middleware $middleware): void { $middleware->trustProxies(at: [ '10.0.0.0/8', ]); })
Sur le site en ligne, request()->secure() doit renvoyer true. Réservez '*' aux cas où l'application n'est joignable que par le proxy. Avec Cloudflare, préférez le mode SSL "Full (strict)" à "Flexible". Enfin, n'activez aucun cache de page sur les pages qui contiennent un formulaire (connexion, inscription, contact) : une page mise en cache sert le jeton d'un autre visiteur. Un en-tête cf-cache-status: HIT sur une telle page est un signal d'alerte.
Le cache de configuration est-il à jour ?
Après config:cache, Laravel ignore les modifications du .env. Videz-le, et voyez pourquoi env() renvoie null après un déploiement :
php artisan config:clear
Et les webhooks ?
Un service externe (paiement, SMS) ne peut pas envoyer de jeton CSRF. Excluez uniquement ses routes, jamais '*' :
// bootstrap/app.php $middleware->validateCsrfTokens(except: [ 'stripe/*', 'webhooks/sms', ]);
Authentifiez ces routes autrement (signature du message) puisqu'elles échappent au contrôle CSRF.
- 419 = jeton CSRF absent ou différent de celui de la session.
- Vérifiez dans l'ordre :
@csrf, en-tête AJAX, expiration, cookie, stockage des sessions, proxy, cache de page,config:clear. - Ne désactivez jamais le contrôle CSRF globalement : excluez seulement les routes de webhooks.
Source : Laravel 419 Page Expired: Causes and Fixes.
Questions fréquentes
Que veut dire l'erreur 419 sous Laravel ?
Le jeton CSRF de la requête ne correspond pas à celui de la session : il est absent, périmé ou la session n'est pas retrouvée.
Peut-on désactiver la protection CSRF pour faire disparaître le 419 ?
Non, ce serait une faille. Corrigez la cause, et n'excluez du contrôle que les routes de webhooks externes, avec une autre forme d'authentification.
Pourquoi le 419 apparaît-il seulement en production ?
Souvent à cause du cookie de session (domaine, SESSION_SECURE_COOKIE, www ou non), d'un proxy HTTPS non déclaré, d'un cache de page ou d'un config:cache qui ignore le nouveau .env.