Skip to content

CSRF Protection

Making Requests

Laravel automatically includes the proper CSRF token when making requests via Inertia or Axios. However, if you’re using Laravel, be sure to omit the csrf-token meta tag from your project, as this will prevent the CSRF token from refreshing properly.

If your server-side framework includes cross-site request forgery (CSRF) protection, you’ll need to ensure that each Inertia request includes the necessary CSRF token for POST, PUT, PATCH, and DELETE requests.

Of course, as already discussed, some server-side frameworks such as Laravel automatically handle the inclusion of the CSRF token when making requests. Therefore, no additional configuration is required when using one of these frameworks.

ASP.NET Core Setup

ASP.NET Core requires manually configuring the anti-forgery service. We recommend setting the header name to X-XSRF-TOKEN to match the default header name used by Axios:

builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

We also recommend adding the following middleware to your application startup:

app.UseInertia();
app.UseMiddleware<CsrfMiddleware>();

The CsrfMiddleware class validates the CSRF token on non-safe methods and ensures the token is available for Axios on subsequent requests. If the token is invalid, the middleware redirects back with a flash message — returning a 409 for Inertia requests or a 303 for full page requests.

using InertiaCore;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace App.Middleware;
public class CsrfMiddleware
{
private readonly RequestDelegate _next;
public CsrfMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var logger = context.RequestServices.GetRequiredService<ILogger<CsrfMiddleware>>();
var antiforgery = context.RequestServices.GetRequiredService<IAntiforgery>();
var method = context.Request.Method.ToUpperInvariant();
var isSafeMethod = method == "GET" || method == "HEAD" || method == "OPTIONS" || method == "TRACE";
// Validate CSRF for state-changing HTTP methods
if (!isSafeMethod)
{
try
{
await antiforgery.ValidateRequestAsync(context);
}
catch (AntiforgeryValidationException ex)
{
var isInertiaRequest = context.Request.Headers["X-Inertia"].ToString() == "true";
var referrer = context.Request.Headers["Referer"].ToString() ?? "/";
var locationKey = isInertiaRequest ? "X-Inertia-Location" : "Location";
logger.LogWarning("CSRF Middleware: Token validation failed - {Message}", ex.Message);
// Put flash message into TempData
var factory = context.RequestServices.GetRequiredService<ITempDataDictionaryFactory>();
var tempData = factory.GetTempData(context);
tempData["flash.error"] = "The page expired, please try again.";
tempData.Save();
context.Response.StatusCode = isInertiaRequest ? 409 : 303;
context.Response.Headers[locationKey] = referrer;
return;
}
}
var tokens = antiforgery.GetAndStoreTokens(context);
// Put the *request* token into a cookie that JS can read so Axios can echo it back.
// IMPORTANT: httpOnly=false so the browser will expose it to JS (Axios).
var cookieOptions = new CookieOptions
{
HttpOnly = false,
Secure = false, // true in production
SameSite = SameSiteMode.Strict, // use None if cross-site (and keep Secure=true)
Path = "/"
};
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!, cookieOptions);
await _next(context);
}
}

This ensures each Inertia request includes the necessary CSRF token for POST, PUT, PATCH, and DELETE requests.

Manual Handling

If you need to handle CSRF protection manually, one approach is to include the CSRF token as a prop on every response. You can then use the token when making Inertia requests.

import { router, usePage } from "@inertiajs/vue3";
const page = usePage();
router.post("/users", {
_token: page.props.csrf_token,
name: "John Doe",
email: "john.doe@example.com",
});

You can even use Inertia’s shared data functionality to automatically include the csrf_token with each response.

A better approach is to use Inertia’s built-in XSRF token handling. Inertia’s HTTP client automatically checks for the existence of an XSRF-TOKEN cookie and, when present, includes the token in an X-XSRF-TOKEN header for every request it makes.

The easiest way to implement this is using server-side middleware. Simply include the XSRF-TOKEN cookie on each response, and then verify the token using the X-XSRF-TOKEN header sent in the requests from Inertia.

You may customize the cookie and header names via the http option in createInertiaApp.

createInertiaApp({
http: {
xsrfCookieName: 'MY-XSRF-TOKEN',
xsrfHeaderName: 'X-MY-XSRF-TOKEN',
},
// ...
})

Handling Mismatches

When a CSRF token mismatch occurs, your server-side framework will likely throw an exception that results in an error response. For example, when using Laravel, a TokenMismatchException is thrown which results in a 419 error page. Since that isn’t a valid Inertia response, the error is shown in a modal.

Obviously, this isn’t a great user experience. A better way to handle these errors is to return a redirect back to the previous page, along with a flash message that the page expired. This will result in a valid Inertia response with the flash message available as a prop which you can then display to the user. Of course, you’ll need to share your flash messages with Inertia for this to work.

When using Laravel, you may modify your application’s exception handler to automatically redirect the user back to the page they were previously on while flashing a message to the session. To accomplish this, you may use the respond exception method in your application’s bootstrap/app.php file.

use Symfony\Component\HttpFoundation\Response;
->withExceptions(function (Exceptions $exceptions) {
$exceptions->respond(function (Response $response) {
if ($response->getStatusCode() === 419) {
return back()->with([
'message' => 'The page expired, please try again.',
]);
}
return $response;
});
});

If you’re using ASP.NET Core with the CsrfMiddleware shown above, this is already handled — token mismatches automatically redirect back to the previous page with a flash.error message in TempData.

The end result is a much better experience for your users. Instead of seeing the error modal, the user is instead presented with a message that the page “expired” and are asked to try again.