CSRF Protection
Introduction
Cross-site request forgeries are a type of malicious exploit whereby unauthorized commands are performed on behalf of an authenticated user. Slenix makes it easy to protect your application from cross-site request forgery (CSRF) attacks.
An Explanation of the Vulnerability
To understand how this vulnerability can be exploited, consider an example. Imagine your application has a /user/email route that accepts a POST request to change the authenticated user's email address. Without CSRF protection, a malicious website could create an HTML form that points to your application's route and submits the attacker's own email address:
<form action="https://your-application.com/user/email" method="POST">
<input type="email" value="malicious@example.com">
</form>
<script>
document.forms[0].submit();
</script>If the malicious website automatically submits the form when the page is loaded, the attacker only needs to lure an unsuspecting user of your application to visit their website — and that user's email address will be silently changed.
To prevent this vulnerability, Slenix inspects every incoming POST, PUT, PATCH, or DELETE request for a secret session value that a malicious application is unable to access.
Preventing CSRF Requests
Slenix automatically generates a CSRF token for each active user session. This token is used to verify that the authenticated user is the one actually making requests to the application. Since the token is stored in the user's session and changes each time the session is regenerated, a malicious application cannot access it.
The current session's CSRF token can be accessed via the csrf_token() helper function:
use Slenix\Http\Routing\Router;
Router::get('/token', function ($request, $response) {
$token = csrf_token();
// use $token as needed
});Any time you define a POST, PUT, PATCH, or DELETE HTML form in your application, you should include a hidden CSRF _csrf_token field so that the CSRF protection can validate the request. For convenience, you may use the @csrf Luna directive to generate the hidden token input field:
<form method="POST" action="/profile">
@csrf
<!-- Equivalent to... -->
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
</form>Slenix automatically validates the CSRF token on every POST, PUT, PATCH, and DELETE request dispatched through the router. When the token in the request matches the token stored in the session, Slenix knows that the authenticated user is the one initiating the request. If the tokens do not match, a 419 status code is returned.
Regenerating the Token
You may regenerate the CSRF token at any time — for example, after a user logs in or logs out — using the CSRF::regenerate() method:
use Slenix\Supports\Security\CSRF;
CSRF::regenerate();Note: Regenerating the token invalidates the previous token immediately. Any open forms rendered before the regeneration will fail validation on submission.
Excluding URIs From CSRF Protection
Sometimes you may wish to exclude a set of URIs from CSRF protection. For example, if you are using Stripe to process payments and utilizing their webhook system, you will need to exclude your Stripe webhook handler from CSRF protection since Stripe will not know what CSRF token to send.
You may exclude specific URIs by calling CSRF::except() in your application's bootstrap file, passing an array of URI patterns. Wildcards (*) are supported:
use Slenix\Supports\Security\CSRF;
CSRF::except([
'/api/*',
'/webhook/stripe',
'/webhook/*',
]);Alternatively, you may place webhook or API routes outside the standard web routing by using a dedicated routes file that is bootstrapped without CSRF validation.
X-CSRF-TOKEN
In addition to checking for the CSRF token as a POST parameter, Slenix also checks for the X-CSRF-TOKEN request header on every state-mutating request. This makes it straightforward to protect AJAX-based applications.
Store the token in an HTML <meta> tag using the CSRF::meta() helper or the csrf_token() function:
<head>
<meta name="csrf-token" content="{{ csrf_token() }}">
</head>You can then read the token from JavaScript and attach it to all outgoing requests. With the native fetch API:
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch('/profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': token,
},
body: JSON.stringify({ name: 'Cláudio' }),
});With Axios, you can configure the header globally so every request includes it automatically:
const token = document.querySelector('meta[name="csrf-token"]').content;
axios.defaults.headers.common['X-CSRF-TOKEN'] = token;With jQuery's $.ajaxSetup:
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});X-XSRF-TOKEN
Slenix also accepts the X-XSRF-TOKEN request header, which is the convention used by some JavaScript frameworks and HTTP clients such as Angular and Axios on same-origin requests.
To use this header, store the token in a JavaScript-accessible cookie or pass it via a <meta> tag and read it in your client-side code:
// Axios reads XSRF-TOKEN cookie automatically on same-origin requests.
// For manual usage:
const token = getCookie('XSRF-TOKEN');
fetch('/settings', {
method: 'PUT',
headers: {
'X-XSRF-TOKEN': token,
},
});CSRF in Luna Templates
Luna provides the @csrf directive as a shorthand for generating the hidden token field. Use it inside every form that submits to a POST, PUT, PATCH, or DELETE route:
<form method="POST" action="{{ route('profile.update') }}">
@csrf
@method('PUT')
<input type="text" name="name" value="@old('name')">
<button type="submit">Save</button>
</form>The @csrf directive compiles to:
<input type="hidden" name="_csrf_token" value="a1b2c3...">CSRF and the Router
Slenix validates the CSRF token automatically inside Router::dispatch() before invoking the route handler. No middleware configuration is required for standard web routes.
The validation runs for all state-mutating HTTP methods:
| Method | CSRF Validated |
|---|---|
GET | No |
HEAD | No |
OPTIONS | No |
POST | Yes |
PUT | Yes |
PATCH | Yes |
DELETE | Yes |
If validation fails, the router returns a 419 response:
// From Router::dispatch() — runs automatically before every handler
if (self::shouldValidateCsrf($method)) {
if (!self::validateCsrfToken()) {
$response->status(419);
throw new \RuntimeException('419 — CSRF token invalid or expired.');
}
}Skipping CSRF Validation for API Routes
If you are building an API that authenticates via JWT or another token-based mechanism, you may want to exclude your API routes entirely. Place them in a route group with a prefix of api/ and register the pattern in CSRF::except():
use Slenix\Supports\Security\CSRF;
use Slenix\Http\Routing\Router;
// Bootstrap — runs before Router::dispatch()
CSRF::except(['/api/*']);
// routes/web.php
Router::group(['prefix' => 'api/v1', 'middleware' => ['jwt']], function () {
Router::get('/users', [Api\UserController::class, 'index']);
Router::post('/users', [Api\UserController::class, 'store']);
Router::put('/users/{id}', [Api\UserController::class, 'update']);
});CSRF Helper Reference
The Slenix\Supports\Security\CSRF class exposes the following static methods:
| Method | Returns | Description |
|---|---|---|
CSRF::token() | string | Returns the current session CSRF token, generating one if absent |
CSRF::regenerate() | string | Generates and stores a new token, returning it |
CSRF::verify() | bool | Returns true if the request token matches the session token |
CSRF::verifyOrFail() | void | Throws a RuntimeException with status 419 if verification fails |
CSRF::field() | string | Returns a hidden <input> element containing the token |
CSRF::meta() | string | Returns a <meta name="csrf-token"> tag for use in JavaScript |
CSRF::fieldAndMeta() | string | Returns both the meta tag and the hidden field |
CSRF::isSafeMethod() | bool | Returns true for GET, HEAD, OPTIONS |
CSRF::shouldVerify() | bool | Returns true for all non-safe methods |
CSRF::except(array) | void | Registers URI patterns to exclude from CSRF validation |
CSRF::isExcluded() | bool | Returns true if the current URI matches an exclusion pattern |
Global Helper Functions
Slenix also provides global helper functions for use in templates and controllers:
// Returns the raw token string
$token = csrf_token();
// Returns the full <input type="hidden"> field
$field = csrf_field();In Luna templates, prefer the directives:
@csrf {{-- hidden input field --}}
{{ csrf_token() }} {{-- raw token string --}}