-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Description
Is there an existing issue for this?
- I have searched the existing issues
Is your feature request related to a problem? Please describe the problem.
Implement a modern, no-token-based CSRF protection algorithm using Fetch Metadata headers (Sec-Fetch-Site) as the primary defense, with fallback to the existing token-based approach for legacy clients.
For example in Go 1.25 a type CrossOriginProtection appeared.
Describe the solution you'd like
Fetch Metadata headers are widely available and works across many devices and browser versions. It’s been available across browsers since March 2023. See compatibility at Sec-Fetch-Site docs.
New algorithm is also not dependant on DataProtection, so it should be nearly no-op and more performant than the token-based approach.
The algorithm boils down to the following steps:
- Allow all GET, HEAD, or OPTIONS requests
These are safe methods, and are assumed not to change state at various layers of the stack already. - If the Origin header matches an allow-list of trusted origins, allow the request.
Trusted origins should be configured as full origins (e.g. https://example.com) and compared by simple equality with the header value. - If the Sec-Fetch-Site header is present, if its value is same-origin or none, allow the request; otherwise, reject the request.
This secures all major up-to-date browsers for sites hosted on trustworthy (HTTPS or localhost) origins. - If neither the Sec-Fetch-Site nor the Origin headers are present, allow the request.
These requests are not from (post-2020) browsers, and can’t be affected by CSRF. - If the Origin header’s host (including the port) matches the Host header, allow the request, otherwise reject it.
This is either a request to an HTTP origin, or by an out-of-date browser.
Additional context
Proposal
Antiforgery today works and is deployed accross many aspnetcore apps. Introducing a conceptually new algorithm as a separate package, API and middleware will be burden for every user to opt-in to the new behavior.
Instead, I am proposing a modification to the provided algorithm (as above) to use existing token-based Antiforgery as a fallback mechanism.
The pseudo code is the following:
public CrossOriginValidationResult Validate(HttpContext context)
{
ArgumentNullException.ThrowIfNull(context);
var request = context.Request;
var headers = request.Headers;
// Get the Origin header value
var origin = headers.Origin.ToString();
var hasOrigin = !string.IsNullOrEmpty(origin) && !string.Equals(origin, "null", StringComparison.OrdinalIgnoreCase);
// Step 1: Check if Origin is in trusted origins list
if (hasOrigin && IsTrustedOrigin(origin))
{
return CrossOriginValidationResult.Allowed;
}
// Step 2: Check Sec-Fetch-Site header (modern browsers)
var secFetchSite = headers[SecFetchSiteHeaderName].ToString();
if (!string.IsNullOrEmpty(secFetchSite))
{
// "same-origin" means the request is from the same origin
// "none" means the request was user-initiated (e.g., bookmark, typed URL)
if (string.Equals(secFetchSite, SecFetchSiteSameOrigin, StringComparison.OrdinalIgnoreCase) ||
string.Equals(secFetchSite, SecFetchSiteNone, StringComparison.OrdinalIgnoreCase))
{
return CrossOriginValidationResult.Allowed;
}
// "cross-site" or "same-site" means the request is from a different origin/site
// This is a potential CSRF attack
if (string.Equals(secFetchSite, SecFetchSiteCrossSite, StringComparison.OrdinalIgnoreCase) ||
string.Equals(secFetchSite, SecFetchSiteSameSite, StringComparison.OrdinalIgnoreCase))
{
return CrossOriginValidationResult.Denied;
}
// Unknown Sec-Fetch-Site value - treat as suspicious
return CrossOriginValidationResult.Denied;
}
// Step 3: Fallback to Origin vs Host comparison (older browsers)
if (hasOrigin)
{
if (OriginMatchesHost(origin, headers.Host.ToString()))
{
return CrossOriginValidationResult.Allowed;
}
// Origin doesn't match Host - this is a cross-origin request
return CrossOriginValidationResult.Denied;
}
// Step 4: Neither Sec-Fetch-Site nor Origin is present
// This could be:
// - A non-browser request (curl, Postman, API client)
// - A very old browser
// - A privacy extension that strips headers
// Return Unknown to trigger fallback to token-based validation
return CrossOriginValidationResult.Unknown;
}API Proposal
+ public class CrossOriginAntiforgeryOptions
+{
+ public IList<string> TrustedOrigins { get; } = new List<string>();
+ public bool FallbackToTokenValidation { get; set; } = true;
+ }
+ public enum CrossOriginValidationResult
+ {
+ Allowed,
+ Denied,
+ Unknown
+ }
+ public interface ICrossOriginAntiforgery
+ {
+ public CrossOriginValidationResult Validate(HttpContext context);
+ }