Aller au contenu principal
Making DigitalMaking Digital
Note technique

Laravel 419 Page Expired : les causes et comment les corriger

En bref

L'erreur 419 "Page Expired" signifie que Laravel n'a pas pu faire correspondre le jeton CSRF de la requête à celui de la session. Vérifiez dans l'ordre : `@csrf` dans le formulaire, l'en-tête `X-CSRF-TOKEN` en AJAX, l'expiration de la session, le cookie de session, le stockage des sessions, le proxy, le cache de page et le cache de configuration.

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.

Blade
<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 :

HTML
<meta name="csrf-token" content="{{ csrf_token() }}">
JavaScript
// 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.

Vérifiez ces réglages dans le .env :

.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/sessions doit exister et être accessible en écriture.
  • Pilote database : la table sessions doit exister (php artisan migrate:status, ou php artisan make:session-table si la migration manque).
  • Pilote redis : l'extension PhpRedis ou predis/predis doit ê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 :

PHP
// 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 :

Terminal
php artisan config:clear

Et les webhooks ?

Un service externe (paiement, SMS) ne peut pas envoyer de jeton CSRF. Excluez uniquement ses routes, jamais '*' :

PHP
// bootstrap/app.php
$middleware->validateCsrfTokens(except: [
    'stripe/*',
    'webhooks/sms',
]);

Authentifiez ces routes autrement (signature du message) puisqu'elles échappent au contrôle CSRF.

À retenir
  • 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.

Making Digital

Nous connectons les logiciels des PME, automatisons leurs tâches répétitives et créons des assistants IA, depuis 2010. Nos articles partent de nos projets réels.

Notre parcours
Une question sur ce sujet ?

Parlons-en 30 minutes.

Vous saurez si votre problème se règle, comment, et en combien de temps. Sans engagement.