3

With ASP.net MVC 5 comes Account Controller and Account Views. In Register view there is line

@Html.AntiForgeryToken()

And above register controller there is attribute:

[ValidateAntiForgeryToken]

I've decided to send data to server from javascript using JSON (I'm using Angular and $http.post method).

My question is how can I send this AntiForgeryToken using javascript and Validate it in controller ?

And are there any more security measures I should include ?

ericdc
  • 10,727
  • 4
  • 23
  • 33
hyperN
  • 2,524
  • 6
  • 49
  • 87
  • 1
    I *think* you'd just need to look for the input with the name "__RequestVerificationToken" and submit that with your post data (using the same name). The controller should handle all of the verification logic. – MikeSmithDev Dec 12 '13 at 01:42
  • Please can you tell me how to do that ? (look for the input) – hyperN Dec 12 '13 at 01:47
  • 1
    `document.getElementsByName("__RequestVerificationToken")[0].value` – MikeSmithDev Dec 12 '13 at 01:50
  • 1
    Hm some searching led me to [this SO post](http://stackoverflow.com/a/17988051/1810243) which is what I was thinking about. That links [to this old SO post](http://stackoverflow.com/questions/2906754/how-can-i-supply-an-antiforgerytoken-when-posting-json-data-using-ajax). – MikeSmithDev Dec 12 '13 at 01:55
  • Thank you very much, I've done it :) – hyperN Dec 12 '13 at 02:16

1 Answers1

8

AngularJS has built-in support for XSRF (AKA anti forgery)

XSRF is a technique by which an unauthorized site can gain your user's private data. Angular provides a mechanism to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie (by default, XSRF-TOKEN) and sets it as an HTTP header (X-XSRF-TOKEN). Since only JavaScript that runs on your domain could read the cookie, your server can be assured that the XHR came from JavaScript running on your domain. The header will not be set for cross-domain requests.

If you want to take advantage of this functionality, you have to create action filters that will create and verify the XSRF-TOKEN cookie. Here is a simplified version of what I use.

AntiForgeryTokenCookieAttribute - use this attribute to add the XSRF-TOKEN cookie

/// <summary>
/// Create a XSRF token in the XSRF-TOKEN cookie which is automatically read by AngularJS
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AntiForgeryTokenCookieAttribute : ActionFilterAttribute
{
    private readonly IAntiForgeryVerificationTokenStore _verificationTokenStore = new AntiForgeryVerificationTokenCookieStore(); //TODO: make configurable
    private const string CookieName = "XSRF-TOKEN"; //TODO: make configurable

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        var oldVerificationToken = _verificationTokenStore.GetVerificationToken(filterContext.HttpContext.Request);

        string newVerificationToken;
        string newToken;
        AntiForgery.GetTokens(oldVerificationToken, out newVerificationToken, out newToken);

        if (newVerificationToken != null)
        {
            _verificationTokenStore.StoreVerificationToken(filterContext.HttpContext.Response, newVerificationToken);
        }

        filterContext.HttpContext.Response.Cookies.Add(new HttpCookie(CookieName, newToken));
    }
}

ValidateAntiForgeryTokenHeaderAttribute

/// <summary>
/// Validate the XSRF token stored in the X-XSRF-TOKEN header.
/// If the header doesn't exist, look for the XSRF token in the from post.
/// 
/// Compatible with ValidateAntiForgeryTokenAttribute
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateAntiForgeryTokenHeaderAttribute : FilterAttribute, IAuthorizationFilter
{
    private readonly IAntiForgeryVerificationTokenStore _verificationTokenStore = new AntiForgeryVerificationTokenCookieStore(); //TODO: make configurable
    private const string TokenHeaderName = "X-XSRF-TOKEN"; //TODO: make configurable

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var token = filterContext.HttpContext.Request.Headers[TokenHeaderName];
        if (token != null)
        {
            //validate the token stored in header
            var verificationToken = _verificationTokenStore.GetVerificationToken(filterContext.HttpContext.Request);
            if (verificationToken == null) { throw new HttpAntiForgeryException("Required verification token not found"); }
            AntiForgery.Validate(verificationToken, token);
        }
        else
        {
            //validate the token stored in form. Same as ValidateAntiForgeryTokenAttribute
            AntiForgery.Validate();
        }
    }
}

IAntiForgeryVerificationTokenStore

public interface IAntiForgeryVerificationTokenStore
{
    string GetVerificationToken(HttpRequestBase request);
    void StoreVerificationToken(HttpResponseBase response, string token);
}

AntiForgeryVerificationTokenCookieStore

public class AntiForgeryVerificationTokenCookieStore : IAntiForgeryVerificationTokenStore
{
    public string GetVerificationToken(HttpRequestBase request)
    {
        if (request == null) { throw new ArgumentNullException("request"); }

        var token = request.Cookies[AntiForgeryConfig.CookieName];
        return token != null ? token.Value : null;
    }

    public void StoreVerificationToken(HttpResponseBase response, string token)
    {
        if (response == null) { throw new ArgumentNullException("response"); }
        if (token == null) { throw new ArgumentNullException("token"); }

        response.Cookies.Add(new HttpCookie(AntiForgeryConfig.CookieName, token) { HttpOnly = true, Secure = AntiForgeryConfig.RequireSsl });
    }
}
LostInComputer
  • 14,252
  • 4
  • 37
  • 47