Skip to content

New Cross-Site Request Forgery Algorithm based on Fetch Metadata headers #65127

@DeagleGross

Description

@DeagleGross

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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);
+ }

Additional links

Metadata

Metadata

Assignees

Labels

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions