Skip to content

[6.x] Fix /!/nocache and /!/csrf CSRF exemption on Laravel 13#14533

Merged
jasonvarga merged 1 commit intostatamic:6.xfrom
ynamite:fix/nocache-csrf-laravel-13
Apr 22, 2026
Merged

[6.x] Fix /!/nocache and /!/csrf CSRF exemption on Laravel 13#14533
jasonvarga merged 1 commit intostatamic:6.xfrom
ynamite:fix/nocache-csrf-laravel-13

Conversation

@ynamite
Copy link
Copy Markdown
Contributor

@ynamite ynamite commented Apr 22, 2026

Laravel 13 renamed the CSRF middleware to Illuminate\Foundation\Http\Middleware\PreventRequestForgery. VerifyCsrfToken and ValidateCsrfToken are now @deprecated aliases that extend PreventRequestForgery.

routes/web.php exempts the legacy classes on /!/nocache and /!/csrf:

->withoutMiddleware(['App\Http\Middleware\VerifyCsrfToken', 'Illuminate\Foundation\Http\Middleware\VerifyCsrfToken'])

Laravel's Router::resolveMiddleware filters excluded middleware via ReflectionClass::isSubclassOf — asking "is the registered middleware a subclass of anything in the excluded list?" On Laravel 13, the registered middleware is PreventRequestForgery and the excluded classes are its subclasses, not its parents, so the check returns false and the exemption is a no-op. PreventRequestForgery::handle runs against an unauthenticated POST, throws TokenMismatchException, and Laravel returns 419.

Symptoms

  • With STATAMIC_STATIC_CACHING_STRATEGY=full, the client-side nocache bootstrap POSTs to /!/nocache and gets 419. Regions never hydrate.
  • /!/csrf (the token-refresh endpoint) has the same stale exempt list and the same failure mode.

Likely related to #10801 (full-cache CSRF mismatch), reported on Laravel 11 / Statamic 5; that symptom surface differs, but the underlying drift — withoutMiddleware no longer matching the active CSRF class — is the same root cause and is tripped again by the Laravel 13 rename.

Repro

Laravel 13 + Statamic 6:

curl -X POST 'http://<app>.test/!/nocache' \
  -H 'Content-Type: application/json' \
  -d '{"url":"http://<app>.test/","sections":["x"]}'
# → HTTP/1.1 419 unknown status

Reproduces with STATAMIC_STATIC_CACHING_STRATEGY=null, so no caching needed.

app('router')->gatherRouteMiddleware($route) on the statamic.nocache route returns [EncryptCookies, AddQueuedCookiesToResponse, StartSession, ShareErrorsFromSession, PreventRequestForgery, SubstituteBindings, NoCacheLocalize]PreventRequestForgery is still in the list.

Fix

Add Illuminate\Foundation\Http\Middleware\PreventRequestForgery to both withoutMiddleware lists. The legacy entries stay for Laravel 10–12 compatibility through the deprecated aliases.

Notes

  • If the CI matrix doesn't already include Laravel 13, this is a good moment to add a cell — the legacy aliases are marked @deprecated and will likely be removed in a future Laravel major, which would turn the existing list into dead entries.
  • Fully backwards compatible: adding a class to the array has no effect on Laravel 10–12 where PreventRequestForgery does not exist in the active stack.

Laravel 13 renamed the CSRF middleware from ValidateCsrfToken to
Illuminate\Foundation\Http\Middleware\PreventRequestForgery. The legacy
VerifyCsrfToken and ValidateCsrfToken classes are now @deprecated
subclasses of PreventRequestForgery.

The route exemptions here use withoutMiddleware([VerifyCsrfToken]),
and Laravel's Router::resolveMiddleware excludes via
ReflectionClass::isSubclassOf — but PreventRequestForgery is the
*parent* of the listed classes, not a subclass, so the check returns
false and the exemption is a no-op. The CSRF check fires on the
unauthenticated POST and Laravel throws TokenMismatchException, so the
client-side nocache bootstrap cannot hydrate regions under full static
caching, and /!/csrf cannot refresh the token either.

Adding PreventRequestForgery to both lists fixes Laravel 13 while
keeping the existing entries for Laravel 10-12 compatibility via the
deprecated aliases.
@ynamite ynamite changed the title Fix /!/nocache and /!/csrf CSRF exemption on Laravel 13 [6.x] Fix /!/nocache and /!/csrf CSRF exemption on Laravel 13 Apr 22, 2026
@jasonvarga jasonvarga merged commit 2e0b577 into statamic:6.x Apr 22, 2026
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants