Bir kaydetme veya sıfırlama işlemi sonrası “Önizleme” butonuna tıkladığınızda, sayfa 419 Hatası (“Page Expired”) döner — sayfa düzgün görünmesine ve açıkça kimlik doğrulamanızın yapılmış olmasına rağmen. İşte bu durumun nedeni ve çözüm için iki satırlık bir düzeltme.
Kurulum
Bir ayarlar sayfası, kaydetme/sıfırlama işlemleri için Inertia.js kullanıyor (router.patch / router.delete) ve canlı bir e-posta önizleme uç noktası için basit bir fetch() POST kullanıyor. Bu fetch, CSRF token’ı manuel olarak meta etiketinden okuyor:
headers: {
'X-CSRF-TOKEN': (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement)?.content ?? '',
},
Bu ilk yüklemede işe yarıyor. Ancak bir kaydetme işleminden sonra 419 hatasına neden oluyor.
Neden Bozuluyor?
Laravel’in CSRF sistemi, oturum bağlamlı bir token ile çalışır. Her yanıtta, Inertia kısmi yanıtları dahil, Laravel XSRF-TOKEN çerezini oturum ile senkronize tutmak amacıyla döndürür.
meta[name="csrf-token"] etiketi, Blade tarafından başlangıç sayfa yüklemesinde bir kez oluşturuluyor:
name="csrf-token" content="{{ csrf_token() }}">
Inertia bu etikete hiç dokunmuyor. CSRF’yi kendisi XSRF-TOKEN çerezi ile yönetiyor. Axios’un yaptığı gibi. Yani bir Inertia yanıtından sonra, çerez mevcut token’ı tutarken, meta etiketinde kalıntı bir değer kalır. Sonraki fetch() çağrısı meta etiketini okuduğunda, eski bir token gönderiyor ve Laravel bunu reddediyor.
İkinci Hata
CSRF durumu düz olsa bile, kod res.text() çağrısını şart olmadan yapıyor:
setPreviewHtml(await res.text());
Laravel 419 hatası döndüğünde, res.text() “Page Expired” HTML hata sayfasını alır. Bu, iframe içinde render edilince, bozuk bir önizleme içeriği olarak görünür ve durumu anlamayı zorlaştırır.
Çözüm
İki değişiklik fetchPreview fonksiyonuna yapılmalıdır:
const fetchPreview = async () => {
setPreviewLoading(true);
try {
// Laravel'in gerçekten güncel tuttuğu token'ı oku — XSRF-TOKEN çerezi
const xsrfToken = decodeURIComponent(
document.cookie
.split('; ')
.find((c) => c.startsWith('XSRF-TOKEN='))
?.split(=)[1] ?? '',
);
const res = await fetch(preview(templateKey).url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': xsrfToken, // ← doğru başlık adı
},
body: JSON.stringify({ locale: activeLocale, body: currentForm.body }),
});
if (!res.ok) { // ← hata HTML'sinin iframe'e sızmasını önler
toast.error(t('common:failed_to_update'));
return;
}
setPreviewHtml(await res.text());
} catch {
toast.error(t(common:failed_to_update));
} finally {
setPreviewLoading(false);
}
};
Yapılan iki değişiklik:
-
X-CSRF-TOKEN→X-XSRF-TOKEN, Blade meta etiketinden kalıntı olan değeri kullanmak yerine Laravel’in güncel tuttuğu çerezden okuma. -
if (!res.ok)koruması – hata HTML’sinin iframe’e sızmasını engeller.
X-XSRF-TOKEN Neden İşe Yarıyor?
Laravel’in VerifyCsrfToken middleware’i geçerli bir token’ı kontrol etmek için iki yerde bakar:
-
_tokenalanı istek gövdesinde -
X-CSRF-TOKENbaşlığı (ham token) -
X-XSRF-TOKENbaşlığı (şifrelenmiş çerez değeri — otomatik olarak çözülüp doğrulanır)
Çerez, HttpOnly: false olarak ayarlanmıştır ki JavaScript bunu okumasın. Inertia, Axios ve artık sizin fetch’leriniz bu aynı mekanizmayı kullanır.
Önemli Nokta
Inertia navigasyonu ile manuel fetch() çağrılarını aynı sayfada karıştırırken:
meta[name="csrf-token"]okumayın — Inertia yanıtlarından sonra bu değer kalıntıdır.XSRF-TOKENçerezini okuyun ve bunuX-XSRF-TOKENolarak gönderin.- Her zaman
res.okdeğerini kontrol edin, kullanıcıya görünür bir yanıt gövdesini render etmeden önce.
Kaynak: Orijinal Makale


