All rules

Sheath rule

security-csrf-field

Forms posting to the Laravel application must include the @csrf directive.
Package
Core
Category
Security
Default severity
error by default
Auto-fix
Auto-fix available

(dangerous)

#Why

CSRF tokens prevent other sites from submitting authenticated requests on a user's behalf. Laravel validates the token before handling state-changing form submissions.

#Examples

#Bad

<!-- Missing @csrf -->
<form action="/users" method="POST">
<input name="name" value="John">
<button>Create</button>
</form>
<!-- Missing @csrf on PUT/PATCH/DELETE -->
<form action="/users/1" method="POST">
@method('DELETE')
<button>Delete</button>
</form>
<!-- A submit control can override a GET form to POST -->
<form id="profile" action="/profile" method="GET"></form>
<button form="profile" formmethod="POST">Save</button>

#Good

<!-- POST form with @csrf -->
<form action="/users" method="POST">
@csrf
<input name="name" value="John">
<button>Create</button>
</form>
<!-- PUT/DELETE forms with both @csrf and @method -->
<form action="/users/1" method="POST">
@csrf
@method('DELETE')
<button>Delete</button>
</form>
<!-- GET forms don't need @csrf -->
<form action="/search" method="GET">
<input name="q" placeholder="Search...">
<button>Search</button>
</form>
<!-- Cross-origin POSTs are handled by the destination, not Laravel's middleware -->
<form action="https://payments.example/checkout" method="POST">
<button>Pay</button>
</form>
<!-- POST override protected by a token in its form owner -->
<form id="profile" action="/profile" method="GET">
@csrf
</form>
<button form="profile" formmethod="POST">Save</button>
<!-- Client interception owns the request and its CSRF mechanism -->
<form method="POST" wire:submit="save">
<button type="submit">Save</button>
</form>
<form method="POST" x-data @submit.prevent="save()">
<button type="submit">Save</button>
</form>

#Non-POST Submissions Are Exempt

Forms with method="GET" don't require CSRF protection because:

  • GET requests should not modify data
  • They're used for reading/searching, not writing
  • Browsers handle them differently

HTML also defaults missing, empty, and invalid form method values to GET. method="PUT", for example, does not make a browser submit PUT; Laravel forms use method="POST" plus @method('PUT'). A submit-capable <button> or <input> can override its form owner with formmethod="POST", so the rule checks those controls as well, including controls linked by form="id".

#Application Submission Targets

The rule requires a token only when a possible POST target belongs to the Laravel application. It recognizes:

  • Missing, empty, and relative action or formaction values
  • Complete route(), url(), secure_url(), and action() helper echoes
  • Absolute URLs whose host matches config('app.url')
  • Additional exact hosts configured through applicationHosts

Static absolute and protocol-relative URLs with any other host are external and do not need Laravel's CSRF token. An arbitrary dynamic destination is reported without a fix because it may resolve to an application route, while inserting a token could leak it if the destination instead resolves externally.

<?php
'security-csrf-field' => ['error', [
'applicationHosts' => ['admin.example', 'legacy.example'],
]],

Host matching is case-insensitive and ignores ports. A form that has an application POST target plus any external or unresolved submission target is still reported for the application route, but no fix is offered: the inserted field would also be submitted by GET and could send the session token to the other destination.

#Auto-fix

The fixer adds @csrf after the opening form tag:

<!-- Before -->
<form action="/users" method="POST">
<input name="name">
</form>
<!-- After -->
<form action="/users" method="POST">
@csrf
<input name="name">
</form>

The fix is dangerous and requires --dangerous, because adding a valid token can change a form submission from a CSRF rejection into a working state-changing request.

#How CSRF Protection Works

  1. Laravel generates a unique token per session
  2. @csrf renders: <input type="hidden" name="_token" value="...">
  3. Laravel middleware verifies the token matches on submission
  4. Invalid/missing tokens result in 419 HTTP error

#Notes

  • The @csrf directive must render a successful control owned by the form. Tokens inside <template>, <datalist>, or disabled fieldsets do not count; a token inside the first <legend> of a disabled fieldset remains successful.
  • A manual token outside the form counts when form="id" associates it with the form and it is guaranteed to render.
  • {{ csrf_field() }} and {!! csrf_field() !!} are recognized as equivalent Laravel helper calls; strings, methods, and similarly named functions are not
  • A manually written <input name="_token"> counts only when its entire value is a Blade echo consisting solely of a call to Laravel's global csrf_token() helper
  • Dynamic method or formmethod values are treated as potentially POST
  • Dynamic action or formaction values report without a fix unless they are a complete call to a recognized Laravel application URL helper
  • Missing @csrf is not reported when the form body contains dynamic content such as {{ $slot }}, @include, @stack, @yield, a nested component, or a raw echo
  • A POST path durably intercepted by wire:submit, x-on:submit.prevent, or @submit.prevent is not a native form submission, so this rule does not require a hidden token on that path. Non-durable .once, .passive, .outside, and .away listeners remain subject to the rule.

#References

#Related Rules