Antiforgery Protection
Protect state‑changing endpoints (POST/PUT/PATCH/DELETE) from Cross-Site Request Forgery by validating a token that is bound to a user session cookie.
The antiforgery middleware issues:
| Artifact | Default (sample) | Purpose |
|---|---|---|
| Cookie | .Kestrun.AntiXSRF | Stores the cryptographic token (HttpOnly, Secure, SameSite=Lax) |
| Header | X-CSRF-TOKEN | Client echoes the request (header value) for unsafe verbs |
| Endpoint | /csrf-token | Returns a fresh token for single-page apps / JS fetch |
Safe verbs (GET, HEAD, OPTIONS, TRACE) do not require the header. Only unsafe verbs modifying server state are validated.
Full source
Sample script: pwsh/tutorial/examples/10.1-Antiforgery.ps1
<#
FileName: 10.1-Antiforgery.ps1 (with antiforgery)
#>
param(
[int]$Port = 5000,
[IPAddress]$IPAddress = [IPAddress]::Loopback
)
# 1. Logging
New-KrLogger |
Set-KrLoggerLevel -Value Debug |
Add-KrSinkConsole |
Register-KrLogger -Name 'console' -SetAsDefault
# 2. Server
New-KrServer -Name 'Simple Server'
# 3. Listener
Add-KrEndpoint -Port $Port -IPAddress $IPAddress -SelfSignedCert
# Required to run PowerShell routes/middleware
# --- Antiforgery: modern SPA preset ---
# Cookie: .Kestrun.AntiXSRF (HttpOnly, Secure, SameSite=Lax)
# Header: X-CSRF-TOKEN
Add-KrAntiforgeryMiddleware -CookieName '.Kestrun.AntiXSRF' -HeaderName 'X-CSRF-TOKEN'
Enable-KrConfiguration
# Add token endpoint
Add-KrAntiforgeryTokenRoute -Path '/csrf-token' | Out-Null
# ----- Sample routes (GETs don’t need tokens; they’re safe by convention) -----
Add-KrMapRoute -Verbs Get -Path '/hello' -ScriptBlock {
Write-KrTextResponse -InputObject 'Hello, World!' -StatusCode 200
}
Add-KrMapRoute -Verbs Get -Path '/hello-json' -ScriptBlock {
Write-KrJsonResponse -InputObject @{ message = 'Hello, World!' } -StatusCode 200
}
Add-KrMapRoute -Verbs Get -Path '/hello-xml' -ScriptBlock {
Write-KrXmlResponse -InputObject @{ message = 'Hello, World!' } -StatusCode 200
}
Add-KrMapRoute -Verbs Get -Path '/hello-yaml' -ScriptBlock {
Write-KrYamlResponse -InputObject @{ message = 'Hello, World!' } -StatusCode 200
}
# ----- Your /json route (explicitly opt-out from antiforgery) -----
$options = [Kestrun.Hosting.Options.MapRouteOptions]::new()
$options.Pattern = '/json'
$options.HttpVerbs = [Kestrun.Utilities.HttpVerb[]] @('Post')
$options.ScriptCode = @{
ScriptBlock = {
$message = Get-KrRequestHeader -Name 'message'
Write-KrJsonResponse -InputObject @{ message = $message } -StatusCode 200
}
}
$options.DisableAntiforgery = $true # this remains open (no CSRF validation)
Add-KrMapRoute -Options $options
# ----- Example of a PROTECTED POST (requires X-CSRF-TOKEN header) -----
Add-KrMapRoute -Verbs Post -Path '/profile' -ScriptBlock {
# If the X-CSRF-TOKEN header is missing/invalid, the antiforgery middleware will reject the request.
$body = Get-KrRequestBody
Write-KrJsonResponse -InputObject @{ saved = $true; name = $body.name } -StatusCode 200
}
# Note: No -DisableAntiforgery here → this POST is protected.
Start-KrServer
Step-by-step
- Add middleware and token endpoint early in configuration.
- Client fetches
/csrf-token; server issues antiforgery cookie + returns token. - For unsafe verbs (POST/PUT/PATCH/DELETE), client includes both cookie and
X-CSRF-TOKENheader. - Middleware validates that the header token matches the cookie context.
- If valid → request proceeds; if missing/invalid → 400/403 rejection.
- Safe verbs (GET/HEAD/OPTIONS/TRACE) and routes with
-DisableAntiforgeryskip validation.
Configuration
Add the middleware early (after runtime) and optionally customize names:
Add-KrAntiforgeryMiddleware -CookieName '.Kestrun.AntiXSRF' -HeaderName 'X-CSRF-TOKEN'
Add-KrAntiforgeryTokenRoute -Path '/csrf-token' | Out-Null
Disable antiforgery per route when appropriate (e.g., read-only JSON echo):
$options = [Kestrun.Hosting.Options.MapRouteOptions]::new()
$options.Pattern = '/json'
$options.HttpVerbs = [Kestrun.Utilities.HttpVerb[]] @('get')
$options.Code = {
$message = Get-KrRequestHeader -Name 'message'
Write-KrJsonResponse -InputObject @{ message = $message } -StatusCode 200
}
$options.Language = 'PowerShell'
$options.DisableAntiforgery = $true
Add-KrMapRoute -Options $options
Try it
The sample defines multiple routes; below we exercise each type (safe GETs, opt-out GET, protected POST success/failure).
# 1. Start the sample script (runs until you Ctrl+C)
pwsh .\docs\_includes\examples\pwsh\10.1-Antiforgery.ps1
# 2. Explore SAFE GET routes (no antiforgery header required)
Invoke-RestMethod -Uri https://127.0.0.1:5000/hello -SkipCertificateCheck
Invoke-RestMethod -Uri https://127.0.0.1:5000/hello-json -SkipCertificateCheck
Invoke-RestMethod -Uri https://127.0.0.1:5000/hello-xml -SkipCertificateCheck -Headers @{ 'Accept' = 'application/xml' }
Invoke-RestMethod -Uri https://127.0.0.1:5000/hello-yaml -SkipCertificateCheck -Headers @{ 'Accept' = 'application/x-yaml' }
# 3. Call the /json route (explicitly opted out via DisableAntiforgery)
Invoke-RestMethod -Uri 'https://127.0.0.1:5000/json?message=Ping' -SkipCertificateCheck
# 4. Fetch a CSRF token (sets cookie + returns token JSON)
$response = Invoke-WebRequest -Uri https://127.0.0.1:5000/csrf-token -SessionVariable sess -SkipCertificateCheck
$token = ($response.Content | ConvertFrom-Json).token
# (Optional) Inspect session cookie name/value
$sess.Cookies.GetCookies('https://127.0.0.1:5000') | Where-Object { $_.Name -like '*AntiXSRF' } | Format-List *
# 5. Protected POST SUCCESS (token + cookie)
Invoke-RestMethod -Uri https://127.0.0.1:5000/profile -Method Post -WebSession $sess -SkipCertificateCheck -Headers @{ 'X-CSRF-TOKEN' = $token } -Body '{"name":"Alice"}' -ContentType 'application/json'
# Create a new session for the second request (to simulate a separate request with same cookie) but not the header
$sess2 = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$sess2.Cookies = $sess.Cookies # copy cookie container reference
# 6. Protected POST FAILURE (missing header) – expect 400/403
Invoke-RestMethod -Uri https://127.0.0.1:5000/profile -Method Post -WebSession $sess2 -SkipCertificateCheck -Body '{"name":"Bob"}' -ContentType 'application/json' -SkipHttpErrorCheck
# 7. (Optional) Re-fetch token to demonstrate rotation / idempotence
Invoke-RestMethod -Uri https://127.0.0.1:5000/csrf-token -WebSession $sess -SkipCertificateCheck
Security Notes
| Concern | Guidance |
|---|---|
| SameSite | Lax is balanced for typical form/SPA flows. Use Strict if you do not rely on cross-site top-level navigation. |
| HTTPS | Always serve antiforgery cookies over TLS in production (Secure flag). |
| Token Leakage | Never log raw tokens. Treat them as secrets while valid. |
| Read-Only APIs | For pure JSON GET APIs, antiforgery adds no value—focus on auth instead. |
| APIs with Bearer Tokens | If you ONLY accept Bearer tokens (no cookies), antiforgery is usually unnecessary. |
Need more depth (architecture, SPA patterns, form integration)? See the Antiforgery Deep Dive guide page.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 400/403 on POST | Missing header | Include X-CSRF-TOKEN with value from /csrf-token endpoint |
| Token rotates unexpectedly | Browser clearing cookies or domain mismatch | Check cookie domain/path and dev tools storage |
| Works in curl but not browser | Header not set by frontend HTTP client | Ensure JS fetch/axios includes custom header |
| Token endpoint not found | Token route not added | Call Add-KrAntiforgeryTokenRoute before Start-KrServer |
| Route unexpectedly bypassed | -DisableAntiforgery set | Remove flag for protected endpoints |
References
Return to the Middleware index or the Tutorial index.
Previous / Next
Previous: Middleware Roadmap Next: Rate Limiting (planned)