Razor Pages with Antiforgery
Protect unsafe endpoints using cookie + header antiforgery tokens, and expose a token endpoint.
Full source
File: pwsh/tutorial/examples/11.2-RazorPages-Antiforgery.ps1
<#
Sample Kestrun Server on how to configure antiforgery protection with Razor Pages.
These examples demonstrate how to configure antiforgery protection in a Kestrun server with Razor Pages.
FileName: 11.2-RazorPages-Antiforgery.ps1
#>
param(
[int]$Port = 5000,
[IPAddress]$IPAddress = [IPAddress]::Loopback
)
# Initialize Kestrun root directory
# the default value is $PWD
# This is recommended in order to use relative paths without issues
Initialize-KrRoot -Path $PSScriptRoot
# Create a new logger
New-KrLogger |
Set-KrLoggerLevel -Value Debug |
Add-KrSinkConsole |
Register-KrLogger -SetAsDefault -Name 'DefaultLogger'
# Create a new Kestrun server
New-KrServer -Name 'RazorPages'
# Add a listener on the configured port and IP address
Add-KrEndpoint -Port $Port -IPAddress $IPAddress -SelfSignedCertificate
# Add a Razor Pages handler to the server
Add-KrPowerShellRazorPagesRuntime -RootPath './Assets/Pages'
# Application-wide metadata (AVAILABLE TO ALL RUNSPACES)
$AppInfo = [pscustomobject]@{
Name = 'Kestrun Razor Demo with Antiforgery'
Environment = 'Development'
StartedUtc = [DateTime]::UtcNow
Version = Get-KrVersion -AsString
}
Write-KrLog -Level Information -Message "Starting Kestrun RazorPages server '{name}' version {version} in {environment} environment on {ipaddress}:{port}" `
-Values $AppInfo.Name, $AppInfo.Version, $AppInfo.Environment, $IPAddress, $Port
# Define feature flags for the application
$FeatureFlags = @{
RazorPages = $true
Cancellation = $true
HotReload = $false
}
Write-KrLog -Level Information -Message 'Feature Flags: {featureflags}' -Values $($FeatureFlags | ConvertTo-Json -Depth 3)
# Define a Message of the Day (MOTD) accessible to all pages
$Motd = @'
Welcome to Kestrun.
This message comes from the main server script.
Defined once, visible everywhere.
'@
Write-KrLog -Level Information -Message 'Message of the Day: {motd}' -Values $Motd
# Add SignalR with KestrunHub
Add-KrSignalRHubMiddleware -Path '/hubs/kestrun'
# Add Tasks Service
Add-KrTasksService
# --- 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 Kestrun configuration
Enable-KrConfiguration
# Add token endpoint
Add-KrAntiforgeryTokenRoute -Path '/csrf-token'
Add-KrMapRoute -Verbs Post -Pattern '/api/operation/start' {
# Start a long-running operation as a Task and report progress via SignalR
$seconds = Get-KrRequestQuery -Name 'seconds' -AsInt
# Default to 30 seconds if not specified or invalid
if ($seconds -le 0) { $seconds = 30 }
# Define steps
$stepMs = 500
$steps = [Math]::Ceiling(($seconds * 1000.0) / $stepMs)
# Start the task
$taskId = New-KrTask -ScriptBlock {
Send-KrSignalREvent -EventName 'OperationProgress' -Data @{
TaskId = $TaskId
Progress = 0
Message = 'Started'
Timestamp = (Get-Date)
}
for ($i = 1; $i -le $steps; $i++) {
# Cooperative cancellation (Stop-KrTask triggers this)
if ($TaskCancellationToken.IsCancellationRequested) {
Send-KrSignalREvent -EventName 'OperationComplete' -Data @{
TaskId = $TaskId
Progress = [int](($i - 1) * 100 / $steps)
Message = 'Cancelled'
Timestamp = (Get-Date)
}
return
}
# Sleep to simulate work
Start-Sleep -Milliseconds $stepMs
# Report progress
Send-KrSignalREvent -EventName 'OperationProgress' -Data @{
TaskId = $TaskId
Progress = [int]($i * 100 / $steps)
Message = "Step $i / $steps"
Timestamp = (Get-Date)
}
}
# Report completion
Send-KrSignalREvent -EventName 'OperationComplete' -Data @{
TaskId = $TaskId
Progress = 100
Message = 'Completed'
Timestamp = (Get-Date)
}
} -Arguments @{ steps = $steps; stepMs = $stepMs } -AutoStart
Write-KrJsonResponse -InputObject @{
Success = $true
TaskId = $taskId
Message = 'Task started'
}
}
Add-KrMapRoute -Verbs Post -Pattern '/tasks/cancel' {
$id = Get-KrRequestQuery -Name 'id' -AsString
if ([string]::IsNullOrWhiteSpace($id)) {
Write-KrJsonResponse -StatusCode 400 -InputObject @{
Error = 'Missing id'
}
return
}
Stop-KrTask -Id $id
Write-KrJsonResponse -InputObject @{
Success = $true
TaskId = $id
Message = 'Cancel requested'
}
}
# Start the server asynchronously
Start-KrServer
Step-by-step
- Root: Call
Initialize-KrRootso the sample can use relative paths reliably. - Logging: Create a console logger and set it as default.
- Server: Create a server named
RazorPagesand bind HTTPS with a self-signed certificate. - Razor runtime: Enable PowerShell-backed Razor Pages with
Add-KrPowerShellRazorPagesRuntime. - App state: Define
$AppInfo,$FeatureFlags, and$Motdto be visible to all pages. - Services: Add SignalR hub middleware and the Tasks service.
- Antiforgery: Add antiforgery middleware, enable the pipeline, then expose
/csrf-token. - Routes: Add unsafe POST routes that require a valid antiforgery token, then start the server.
Try it
# A Razor page (self-signed TLS)
curl -k -i https://127.0.0.1:5000/
# Fetch an antiforgery token (returns JSON) and stores the cookie
curl -k -c cookies.txt https://127.0.0.1:5000/csrf-token
# Copy the token value from the JSON response and use it in the header
curl -k -b cookies.txt -X POST \
-H "X-CSRF-TOKEN: <paste-token-here>" \
"https://127.0.0.1:5000/api/operation/start?seconds=3"
PowerShell equivalents:
# Get token + cookie (use the same WebSession for the follow-up POST)
$tokenResponse = Invoke-RestMethod -Uri https://127.0.0.1:5000/csrf-token -SkipCertificateCheck -SessionVariable s
Invoke-RestMethod -Uri 'https://127.0.0.1:5000/api/operation/start?seconds=3' -Method Post -SkipCertificateCheck \
-WebSession $s -Headers @{ 'X-CSRF-TOKEN' = $tokenResponse.token }
Antiforgery flow
This sample uses the “SPA-style” preset:
- Cookie name:
.Kestrun.AntiXSRF - Header name:
X-CSRF-TOKEN - Token route:
/csrf-token(returns JSON like{ token, headerName })
For unsafe verbs (POST/PUT/PATCH/DELETE), call /csrf-token first and then echo the returned token in the configured header while also sending the cookie.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
/csrf-token returns 404 | Token route not added | Ensure Add-KrAntiforgeryTokenRoute is called before Start-KrServer |
| POST returns 400/403 antiforgery error | Missing token header or cookie | Call /csrf-token first; send the cookie and X-CSRF-TOKEN header on the POST |
| Curl fails with certificate error | Self-signed TLS | Use curl -k (demo only) or configure a trusted certificate |
| Antiforgery works in browser but not via curl | Cookies not persisted | Use curl -c cookies.txt then -b cookies.txt for subsequent requests |
References
- Add-KrAntiforgeryMiddleware
- Add-KrAntiforgeryTokenRoute
- Antiforgery Guide
- Initialize-KrRoot
- New-KrLogger
- Set-KrLoggerLevel
- Add-KrSinkConsole
- Register-KrLogger
- New-KrServer
- Add-KrEndpoint
- Add-KrPowerShellRazorPagesRuntime
- Add-KrSignalRHubMiddleware
- Add-KrTasksService
- Enable-KrConfiguration
- Add-KrMapRoute
- Get-KrRequestQuery
- New-KrTask
- Send-KrSignalREvent
- Stop-KrTask
- Write-KrJsonResponse
- Start-KrServer
Previous / Next
Previous: Razor Pages Quickstart Next: None