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).
- When anti-forgery protection is enabled, either the form token or coookie token being empty will cause validation to fail.
- The security tokens in the anti-forgery tokens must match.
- The authorization information in the anti-forgery tokens must be consistent, such as matching usernames.
- The token's own flag must be correct - IsSessionToken being true indicates a cookie token, otherwise it's a form token.
- 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; }
}
}