Search code examples
ajaxasp.net-mvcauthenticationfilterantiforgerytoken

Wrong application token on AuthorizeAttribute Filter


On my AJAX POSTs I send the AntiForgeryToken to validate on my controller actions, and for that I use an AuthorizeAttribute Filter to validate it.

It works just fine on local machine. However, on server it validates the first AntiForgeryToken registered on browser from another site on the same server/domain.

My AuthorizeAttribute filter:

public void OnAuthorization(AuthorizationContext filterContext)
{
    if(filterContext == null)
        throw new HttpAntiForgeryException("filterContext is null");

    if (filterContext.RequestContext == null)
        throw new HttpAntiForgeryException("filterContext.RequestContext is null");

    if (filterContext.RequestContext.HttpContext == null)
        throw new HttpAntiForgeryException("filterContext.RequestContext.HttpContext is null");

    if (filterContext.RequestContext.HttpContext.Request == null)
        throw new HttpAntiForgeryException("filterContext.RequestContext.HttpContext.Request is null");

    if (filterContext.RequestContext.HttpContext.Request.Headers == null)
        throw new HttpAntiForgeryException("filterContext.RequestContext.HttpContext.Request.Headers is null");

    if (filterContext.RequestContext.HttpContext.Request.Cookies == null)
        throw new HttpAntiForgeryException("filterContext.RequestContext.HttpContext.Request.Cookies is null");

    string KEY_NAME = "__RequestVerificationToken";

    string clientToken = filterContext.RequestContext.HttpContext.Request.Headers.Get(KEY_NAME);
    if (clientToken == null)
    {
        throw new HttpAntiForgeryException(string.Format("Header does not contain {0}", KEY_NAME));
    }

    var key = filterContext.HttpContext.Request.Cookies.AllKeys.FirstOrDefault(m => m == KEY_NAME);
    if (string.IsNullOrEmpty(key))
        key = filterContext.HttpContext.Request.Cookies.AllKeys.FirstOrDefault(m => m != null && m.Contains(KEY_NAME + "_"));
    if (string.IsNullOrEmpty(key))
        key = filterContext.HttpContext.Request.Cookies.AllKeys.FirstOrDefault(m => m != null && m.Contains(KEY_NAME));

    var serverToken = filterContext.HttpContext.Request.Cookies.Get(key ?? KEY_NAME);
    if (serverToken == null)
    {
        throw new HttpAntiForgeryException(string.Format("Cookies does not contain {0}", KEY_NAME));
    }

    System.Web.Helpers.AntiForgery.Validate(serverToken.Value, clientToken);
}

My AJAX requests:

$.ajax({
    type: !methodType ? "POST" : methodType,
    url: methodName,
    data: methodType == "get" || methodType == "GET" ? parameters : typeof parameters !== "undefined" && parameters != null ? JSON.stringify(parameters) : null,
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    headers: {
        "__RequestVerificationToken": token
    },
    processdata: true,
    success: succeededHandler,
    async: isAsync,
    crossDomain: false,
    error: errorHandler
})
    .done(function (data) {
    if (doneHandler)
        doneHandler();
})
.always(function (xhr) {
    closeLoadingDialog();

    try {
        if (xhr.status == 401 || (xhr.getResponseHeader("X-Responded-JSON") != null
            && JSON.parse(xhr.getResponseHeader("X-Responded-JSON")).status == "401")) {
            (location ? location : window.location).reload();
            return;
        }
    }
    catch (er) { }
});

I already figured out the problem could be the way I search the serverToken key with the FirstOrDefault(). This is looking for all cookies registered on browser and not only for the application cookie.

But how can I look only for the application cookie? How can I solve this on my AuthorizeAttribute filter?


Solution

  • After many searches and head breaking I found this link that shows a similar way to validate the AntiForgeryToken. But here they use an AntiForgeryConfig.CookieName property to get the application cookie.

    They said:

    CookieName is a static property on the class System.Web.Helpers.AntiForgeryConfig.

    You can see more here: http://msdn.microsoft.com/en-us/library/system.web.helpers.antiforgeryconfig.cookiename(v=vs.111).aspx

    So I changed my code to this:

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        string KEY_NAME = "__RequestVerificationToken";
    
        if (filterContext == null)
            throw new HttpAntiForgeryException("filterContext is null");
    
        if (filterContext.RequestContext == null)
            throw new HttpAntiForgeryException("filterContext.RequestContext is null");
    
        if (filterContext.RequestContext.HttpContext == null)
            throw new HttpAntiForgeryException("filterContext.RequestContext.HttpContext is null");
    
        if (filterContext.RequestContext.HttpContext.Request == null)
            throw new HttpAntiForgeryException("filterContext.RequestContext.HttpContext.Request is null");
    
        if (filterContext.RequestContext.HttpContext.Request.Headers == null)
            throw new HttpAntiForgeryException("filterContext.RequestContext.HttpContext.Request.Headers is null");
    
        if (filterContext.RequestContext.HttpContext.Request.Cookies == null)
            throw new HttpAntiForgeryException("filterContext.RequestContext.HttpContext.Request.Cookies is null");
    
        var request = filterContext.HttpContext.Request;
    
    
        //  Ajax POSTs and normal form posts have to be treated differently when it comes to validating the AntiForgeryToken
        if (request.IsAjaxRequest())
        {
            string clientToken = request.Headers.Get(KEY_NAME);
            if (clientToken == null)
            {
                throw new HttpAntiForgeryException(string.Format("Header does not contain {0}", KEY_NAME));
            }
            var serverToken = request.Cookies[AntiForgeryConfig.CookieName];
            var cookieValue = serverToken != null ? serverToken.Value : KEY_NAME;
    
            AntiForgery.Validate(cookieValue, clientToken);
        }
        else
        {
            new ValidateAntiForgeryTokenAttribute().OnAuthorization(filterContext);
        }
    }
    

    And now works just fine!