Understanding CSRF Token Validation in ASP.NET MVC 5

In ASP.NET MVC 5, applying the [ValidateAntiForgeryToken] attribute to action methods ensures that requests originate from legitimate forms by comparing cookie tokens with form tokens. For debugging purposes, you can directly invoke the validation method using AntiForgery.Validate().

[HttpPost]
public ActionResult ProcessData(int id = 1)
{
    var antiForgeryCookie = Request.Cookies[AntiForgeryConfig.CookieName];
    var cookieValue = antiForgeryCookie?.Value;
    
    AntiForgery.Validate(cookieValue, Request["__RequestVerificationToken"]);
    ModelState.AddModelError("", "Validation completed successfully!");
    return Json("Token validation passed!");
}

Examining the Validate method implementation

/// <summary>Verifies that input data from HTML form fields originates from the submitting user.</summary>
/// <param name="cookieToken">The cookie token value.</param>
/// <param name="formToken">The form token value.</param>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static void Validate(string cookieToken, string formToken)
{
    if (HttpContext.Current == null)
        throw new ArgumentException(WebPageResources.HttpContextUnavailable);
    AntiForgery._worker.Validate(new HttpContextWrapper(HttpContext.Current), cookieToken, formToken);
}

Looking into AntiForgery._worker.Validate method

public void Validate(HttpContextBase httpContext, string cookieToken, string formToken)
{
    this.CheckSSLConfig(httpContext);
    var cookieTokenObj = this.DeserializeToken(cookieToken);
    var formTokenObj = this.DeserializeToken(formToken);
    this._validator.ValidateTokens(httpContext, AntiForgeryWorker.ExtractIdentity(httpContext), cookieTokenObj, formTokenObj);
}

Contniuing with this._validator.ValidateTokens() method

internal interface ITokenValidator
{
    AntiForgeryToken GenerateCookieToken();
    AntiForgeryToken GenerateFormToken(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken cookieToken);
    bool IsCookieTokenValid(AntiForgeryToken cookieToken);
    void ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken cookieToken, AntiForgeryToken formToken);
}

Since ValidateTokens needs implementation, we find the concrete implementation in TokenValidator class

public void ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken)
{
    if (sessionToken == null)
        throw HttpAntiForgeryException.CreateCookieMissingException(this._config.CookieName);
    if (fieldToken == null)
        throw HttpAntiForgeryException.CreateFormFieldMissingException(this._config.FormFieldName);
    if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken)
        throw HttpAntiForgeryException.CreateTokensSwappedException(this._config.CookieName, this._config.FormFieldName);
    if (!object.Equals(sessionToken.SecurityToken, fieldToken.SecurityToken))
        throw HttpAntiForgeryException.CreateSecurityTokenMismatchException();
    
    string userName = string.Empty;
    BinaryBlob claimUid = null;
    if (identity != null && identity.IsAuthenticated)
    {
        claimUid = this._claimUidExtractor.ExtractClaimUid(identity);
        if (claimUid == null)
            userName = identity.Name ?? string.Empty;
    }
    
    bool isUrl = userName.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || 
                userName.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
    
    if (!string.Equals(fieldToken.Username, userName, isUrl ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
        throw HttpAntiForgeryException.CreateUsernameMismatchException(fieldToken.Username, userName);
    
    if (!object.Equals(fieldToken.ClaimUid, claimUid))
        throw HttpAntiForgeryException.CreateClaimUidMismatchException();
    
    if (this._config.AdditionalDataProvider != null && 
        !this._config.AdditionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData))
        throw HttpAntiForgeryException.CreateAdditionalDataCheckFailedException();
}

This method represents a comprehensive validation approach. The core validation logic revolves around two tokens - one from cookies and one from the form. The IsSessionToken property indicates whether the token is a cookie token (true) or form token (false).

  1. When anti-forgery protection is enabled, either the form token or coookie token being empty will cause validation to fail.
  2. The security tokens in the anti-forgery tokens must match.
  3. The authorization information in the anti-forgery tokens must be consistent, such as matching usernames.
  4. The token's own flag must be correct - IsSessionToken being true indicates a cookie token, otherwise it's a form token.
  5. Additional validation checks the AdditionalDataProvider property.

The core of the validation process centers around the AntiForgeryToken sealed class:

internal sealed class AntiForgeryToken
{
    internal const int SecurityTokenBitLength = 128;
    internal const int ClaimUidBitLength = 256;
    private string _additionalData;
    private BinaryBlob _securityToken;
    private string _username;

    public string AdditionalData
    {
        get { return _additionalData ?? string.Empty; }
        set { _additionalData = value; }
    }

    public BinaryBlob ClaimUid { get; set; }

    // True indicates cookie token, false indicates form token
    public bool IsSessionToken { get; set; }

    // Security token
    public BinaryBlob SecurityToken
    {
        get
        {
            if (_securityToken == null)
                _securityToken = new BinaryBlob(128);
            return _securityToken;
        }
        set { _securityToken = value; }
    }

    public string Username
    {
        get { return _username ?? string.Empty; }
        set { _username = value; }
    }
}

Tags: ASP.NET MVC CSRF Protection Token Validation Security .NET Framework

Posted on Wed, 09 Sep 2026 16:47:20 +0000 by LowEndTheory