Skip to content

Commit 8a949ff

Browse files
committed
update csrf documentation
1 parent f0e8ea7 commit 8a949ff

3 files changed

Lines changed: 279 additions & 83 deletions

File tree

v1/advanced/csrf-protection.mdx

Lines changed: 93 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,97 @@ If your server-side framework includes cross-site request forgery (CSRF) protect
1414

1515
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.**
1616

17-
However, 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.
17+
### ASP.NET Core Setup
18+
19+
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:
20+
21+
```csharp
22+
builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
23+
```
24+
25+
We also recommend adding the following middleware to your application startup:
26+
27+
```csharp
28+
app.UseInertia();
29+
app.UseMiddleware<CsrfMiddleware>();
30+
```
31+
32+
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.
33+
34+
```csharp
35+
using InertiaCore;
36+
using Microsoft.AspNetCore.Antiforgery;
37+
using Microsoft.AspNetCore.Mvc.ViewFeatures;
38+
39+
namespace App.Middleware;
40+
41+
public class CsrfMiddleware
42+
{
43+
private readonly RequestDelegate _next;
44+
45+
public CsrfMiddleware(RequestDelegate next)
46+
{
47+
_next = next;
48+
}
49+
50+
public async Task InvokeAsync(HttpContext context)
51+
{
52+
var logger = context.RequestServices.GetRequiredService<ILogger<CsrfMiddleware>>();
53+
var antiforgery = context.RequestServices.GetRequiredService<IAntiforgery>();
54+
var method = context.Request.Method.ToUpperInvariant();
55+
var isSafeMethod = method == "GET" || method == "HEAD" || method == "OPTIONS" || method == "TRACE";
56+
57+
// Validate CSRF for state-changing HTTP methods
58+
if (!isSafeMethod)
59+
{
60+
try
61+
{
62+
await antiforgery.ValidateRequestAsync(context);
63+
}
64+
catch (AntiforgeryValidationException ex)
65+
{
66+
var isInertiaRequest = context.Request.Headers["X-Inertia"].ToString() == "true";
67+
var referrer = context.Request.Headers["Referer"].ToString() ?? "/";
68+
var locationKey = isInertiaRequest ? "X-Inertia-Location" : "Location";
69+
70+
logger.LogWarning("CSRF Middleware: Token validation failed - {Message}", ex.Message);
71+
72+
// Put flash message into TempData
73+
var factory = context.RequestServices.GetRequiredService<ITempDataDictionaryFactory>();
74+
var tempData = factory.GetTempData(context);
75+
tempData["flash.error"] = "The page expired, please try again.";
76+
tempData.Save();
77+
78+
context.Response.StatusCode = isInertiaRequest ? 409 : 303;
79+
context.Response.Headers[locationKey] = referrer;
80+
return;
81+
}
82+
}
83+
84+
var tokens = antiforgery.GetAndStoreTokens(context);
85+
86+
// Put the *request* token into a cookie that JS can read so Axios can echo it back.
87+
// IMPORTANT: httpOnly=false so the browser will expose it to JS (Axios).
88+
var cookieOptions = new CookieOptions
89+
{
90+
HttpOnly = false,
91+
Secure = false, // true in production
92+
SameSite = SameSiteMode.Strict, // use None if cross-site (and keep Secure=true)
93+
Path = "/"
94+
};
95+
96+
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!, cookieOptions);
97+
98+
await _next(context);
99+
}
100+
}
101+
```
102+
103+
This ensures each Inertia request includes the necessary CSRF token for `POST`, `PUT`, `PATCH`, and `DELETE` requests.
104+
105+
### Manual Handling
106+
107+
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.
18108

19109
<CodeGroup>
20110

@@ -100,32 +190,10 @@ use Symfony\Component\HttpFoundation\Response;
100190
});
101191
```
102192

103-
```csharp .NET icon="dotnet"
104-
using InertiaCore;
105-
using Microsoft.AspNetCore.Antiforgery;
106-
107-
// Program.cs — catch antiforgery validation failures and redirect back
108-
// with a flash message instead of surfacing the raw 400 to the client.
109-
app.UseExceptionHandler(errorApp =>
110-
{
111-
errorApp.Run(async context =>
112-
{
113-
var feature = context.Features.Get<IExceptionHandlerFeature>();
114-
if (feature?.Error is AntiforgeryValidationException)
115-
{
116-
Inertia.Share("flash", new { Message = "The page expired, please try again." });
117-
118-
var result = Inertia.Back();
119-
var actionContext = new ActionContext(context, context.GetRouteData(), new ActionDescriptor());
120-
await result.ExecuteResultAsync(actionContext);
121-
return;
122-
}
123-
});
124-
});
125-
```
126-
127193
</CodeGroup>
128194

195+
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.
196+
129197
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.
130198

131199
<video controls><source src="/mp4/csrf-mismatch-warning.mp4" type="video/mp4"></source></video>

v2/security/csrf-protection.mdx

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,97 @@ If your server-side framework includes cross-site request forgery (CSRF) protect
1414

1515
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.**
1616

17-
However, 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.
17+
### ASP.NET Core Setup
18+
19+
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:
20+
21+
```csharp
22+
builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
23+
```
24+
25+
We also recommend adding the following middleware to your application startup:
26+
27+
```csharp
28+
app.UseInertia();
29+
app.UseMiddleware<CsrfMiddleware>();
30+
```
31+
32+
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.
33+
34+
```csharp
35+
using InertiaCore;
36+
using Microsoft.AspNetCore.Antiforgery;
37+
using Microsoft.AspNetCore.Mvc.ViewFeatures;
38+
39+
namespace App.Middleware;
40+
41+
public class CsrfMiddleware
42+
{
43+
private readonly RequestDelegate _next;
44+
45+
public CsrfMiddleware(RequestDelegate next)
46+
{
47+
_next = next;
48+
}
49+
50+
public async Task InvokeAsync(HttpContext context)
51+
{
52+
var logger = context.RequestServices.GetRequiredService<ILogger<CsrfMiddleware>>();
53+
var antiforgery = context.RequestServices.GetRequiredService<IAntiforgery>();
54+
var method = context.Request.Method.ToUpperInvariant();
55+
var isSafeMethod = method == "GET" || method == "HEAD" || method == "OPTIONS" || method == "TRACE";
56+
57+
// Validate CSRF for state-changing HTTP methods
58+
if (!isSafeMethod)
59+
{
60+
try
61+
{
62+
await antiforgery.ValidateRequestAsync(context);
63+
}
64+
catch (AntiforgeryValidationException ex)
65+
{
66+
var isInertiaRequest = context.Request.Headers["X-Inertia"].ToString() == "true";
67+
var referrer = context.Request.Headers["Referer"].ToString() ?? "/";
68+
var locationKey = isInertiaRequest ? "X-Inertia-Location" : "Location";
69+
70+
logger.LogWarning("CSRF Middleware: Token validation failed - {Message}", ex.Message);
71+
72+
// Put flash message into TempData
73+
var factory = context.RequestServices.GetRequiredService<ITempDataDictionaryFactory>();
74+
var tempData = factory.GetTempData(context);
75+
tempData["flash.error"] = "The page expired, please try again.";
76+
tempData.Save();
77+
78+
context.Response.StatusCode = isInertiaRequest ? 409 : 303;
79+
context.Response.Headers[locationKey] = referrer;
80+
return;
81+
}
82+
}
83+
84+
var tokens = antiforgery.GetAndStoreTokens(context);
85+
86+
// Put the *request* token into a cookie that JS can read so Axios can echo it back.
87+
// IMPORTANT: httpOnly=false so the browser will expose it to JS (Axios).
88+
var cookieOptions = new CookieOptions
89+
{
90+
HttpOnly = false,
91+
Secure = false, // true in production
92+
SameSite = SameSiteMode.Strict, // use None if cross-site (and keep Secure=true)
93+
Path = "/"
94+
};
95+
96+
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!, cookieOptions);
97+
98+
await _next(context);
99+
}
100+
}
101+
```
102+
103+
This ensures each Inertia request includes the necessary CSRF token for `POST`, `PUT`, `PATCH`, and `DELETE` requests.
104+
105+
### Manual Handling
106+
107+
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.
18108

19109
<CodeGroup>
20110

@@ -90,36 +180,10 @@ use Symfony\Component\HttpFoundation\Response;
90180
});
91181
```
92182

93-
```csharp .NET icon="dotnet"
94-
using InertiaCore;
95-
using Microsoft.AspNetCore.Antiforgery;
96-
using Microsoft.AspNetCore.Diagnostics;
97-
using Microsoft.AspNetCore.Mvc;
98-
using Microsoft.AspNetCore.Mvc.Abstractions;
99-
using Microsoft.AspNetCore.Mvc.Infrastructure;
100-
101-
// Program.cs — catch antiforgery validation failures and redirect back
102-
// with a flash message instead of surfacing the raw 400 to the client.
103-
app.UseExceptionHandler(errorApp =>
104-
{
105-
errorApp.Run(async context =>
106-
{
107-
var feature = context.Features.Get<IExceptionHandlerFeature>();
108-
if (feature?.Error is AntiforgeryValidationException)
109-
{
110-
Inertia.Flash("message", "The page expired, please try again.");
111-
112-
var result = Inertia.Back();
113-
var actionContext = new ActionContext(context, context.GetRouteData(), new ActionDescriptor());
114-
await result.ExecuteResultAsync(actionContext);
115-
return;
116-
}
117-
});
118-
});
119-
```
120-
121183
</CodeGroup>
122184

185+
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.
186+
123187
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.
124188

125189
<video controls className="w-full rounded-xl" src="/mp4/csrf-mismatch-warning.mp4"></video>

0 commit comments

Comments
 (0)