3

I'm working on an angular 2 app using a ASP.Net MVC backend. For this, the index.cshtml has included @Html.AntiForgeryToken(); so i get back a hidden input element with the Token as its value. In my frontend i can grab this token and use it for my requests. So far so good.

Now since user information is used to generate the token in the backend, i have to switch out the token on some occassions. The problem is described here in this question.

Now since i don't want to do a full page reload in my app i have to use a workaround. For this i created a partial view in the backend which just hands out this input using @Html.AntiForgeryToken(); as ActionResult with a simple method like this:

public ActionResult GetAntiForgery()
{
    return PartialView("~/Views/Home/AntiForgery.cshtml");
}

After login / logout i call this function from my frontend and replace the value of my existing AntiForgery input element like this:

getAntiForgery(url:string) {
        return this._http.get(url)
            .map((response:any) => {
                let originalXsrfElement = <HTMLInputElement>document.querySelector('[name=__RequestVerificationToken]');
                let body = response._body;

                if(body) {
                    let helperElem = document.createElement('div');
                    helperElem.innerHTML = body;
                    let helperInputElem = <HTMLInputElement>helperElem.firstChild;
                    originalXsrfElement.value = helperInputElem.value;
                }

                return response;
            });
    }

I can't even tell you what bugs me the most. I hate to make an extra request (but lets not dive into this here) but way more terrible for me is that i have to request something, get an html string back and have to extract the token string out of it.

If i were the backend guy, i would kill the frontend guy (me..) for even thinking about creating an extra partial view, creating an extra method and always doing two requests on login/logout instead of one.

Is there a better way? For example i would like to call a method which just hands out a JSON with the proper token instead of an HTML snippet. Even better, on existing JsonResult methods in the backend, i would like to add the new CSRF Token as a property.

I'm not a backend architect so there might be some stuff wrong in general how i do this, but my backend colleagues don't mind what i'm doing there so it shouldn't be so far off.

Any hints are appreciated.

Community
  • 1
  • 1
malifa
  • 8,025
  • 2
  • 42
  • 57
  • Looks like this question might help (basically puts anti-forgery into ajax header instead of hidden field): http://stackoverflow.com/questions/15416264/validateantiforgerytoken-with-spa-architecture – JohnnyFun Jun 22 '16 at 13:20
  • Sorry, can't see how this helps with my problem. The question is different. – malifa Jun 22 '16 at 13:39
  • The "Anti-CSRF and AJAX" section in the linked article seems like it could be what you're looking for: http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks – JohnnyFun Jun 22 '16 at 13:55
  • Now i understand what you wanted to tell me, i thought we misunderstood each other. See my comment to the answer below for clarification where my issues are. – malifa Jun 22 '16 at 14:10

1 Answers1

1

You can use AntiForgery.GetToken in your action to set some property on the JSON being returned:

string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
json.RequestVerificationToken = cookieToken + ":" + formToken;

Then, pass it back in the header of your next AJAX request:

$.ajax("/my/awesome/url", {
    type: "post",
    contentType: "application/json",
    data: { ... },
    dataType: "json",
    headers: {
        'RequestVerificationToken': requestVerificationToken
    }
});

Since, it's no longer being handled by cookies, you can't just use the standard ValidateAntiForgeryToken attribute. You'll need to manually check for the header and validate it. However, you should be able to create a custom attribute you can use instead:

AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class ValidateAntiForgeryTokenFromHeaderAttribute : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var request = filterContext.HttpContext.Request;

        string cookieToken = "";
        string formToken = "";

        IEnumerable<string> tokenHeaders;
        if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
        {
            string[] tokens = tokenHeaders.First().Split(':');
            if (tokens.Length == 2)
            {
                cookieToken = tokens[0].Trim();
                formToken = tokens[1].Trim();
            }
        }
        AntiForgery.Validate(cookieToken, formToken);
    }
}

Then, just decorate your actions with [ValidateAntiForgeryTokenFromHeader] instead.

[Code adapted from http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks]

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • What i don't get is, the `@Html.AntiForgeryToken()` call inserts a html snippet, an input with the token as a value. Does this already setup the cookie or why doesn't your suggestion work with cookies anymore? What i want is just to get the value of `@Html.AntiForgeryToken()` and send this string value as a json response instead handing out a view. Your approach seems to be a bit of an overkill for me just for this little "change". Like "Frontend: Server, give me that token string". "Server: No sorry i can only give you an html element...". Wait, what? ;) – malifa Jun 22 '16 at 14:09
  • Typically anything you'd hit via an AJAX request wouldn't support cookies. A well-designed API is going to be REST-based, and REST is stateless (no session, no cookies). Web Api, for sure doesn't support it. – Chris Pratt Jun 22 '16 at 17:07