44

I have an AngularJS Single Page Application (SPA) hosted by an ASP.NET MVC application.
The back-end is ASP.NET Web Api.

I would like to protect it against CSRF attacks by generating an AntiForgeryToken in the ASP.NET MVC part, pass it to AngularJS, and then have Web Api validate the AntiForgeryToken received from the subsequent AngularJS calls.

“Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated. CSRF attacks specifically target state-changing requests, not theft of data, since the attacker has no way to see the response to the forged request. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker's choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.”
- Open Web Application Security Project (OWASP)

Mihai Dinculescu
  • 19,743
  • 8
  • 55
  • 70

3 Answers3

63

Add to the ASP.NET MVC View that serves the AngularJS SPA, let's say Views\Home\Index.cshtml, the HTML helper that generates the AntiForgeryToken.

@Html.AntiForgeryToken();

Configure AngularJS to pass the above generated AntiForgeryToken as Request Header.

angular.module('app')
.run(function ($http) {
    $http.defaults.headers.common['X-XSRF-Token'] =
        angular.element('input[name="__RequestVerificationToken"]').attr('value');
});

Create a custom Web API Filter to validate all non-GET requests (PUT, PATCH, POST, DELETE).

This assumes that all your GET requests are safe and don't need protecting.
If that's not the case, remove the if (actionContext.Request.Method.Method != "GET") exclusion.

using System;
using System.Linq;
using System.Net.Http;
using System.Web.Helpers;
using System.Web.Http.Filters;

namespace Care.Web.Filters
{
    public sealed class WebApiValidateAntiForgeryTokenAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(
            System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            if (actionContext == null)
            {
                throw new ArgumentNullException("actionContext");
            }

            if (actionContext.Request.Method.Method != "GET")
            {
                var headers = actionContext.Request.Headers;
                var tokenCookie = headers
                    .GetCookies()
                    .Select(c => c[AntiForgeryConfig.CookieName])
                    .FirstOrDefault();

                var tokenHeader = string.Empty;
                if (headers.Contains("X-XSRF-Token"))
                {
                    tokenHeader = headers.GetValues("X-XSRF-Token").FirstOrDefault();
                }

                AntiForgery.Validate(
                    tokenCookie != null ? tokenCookie.Value : null, tokenHeader);
            }

            base.OnActionExecuting(actionContext);
        }
    }
}

Register the newly created filter as a global one, in Global.asax.cs.

    private static void RegisterWebApiFilters(HttpFilterCollection filters)
    {
        filters.Add(new WebApiValidateAntiForgeryTokenAttribute());
    }

Alternatively, if you don't wish to add this filter globally, you can put it only on certain Web API actions, like this

[WebApiValidateAntiForgeryToken]

This, of course, is by definition less secure, as there's always the chance that you'll forget to apply the attribute to an action that needs it.

Also, note that you must have the Microsoft.AspNet.WebApi.Core package in order to have access to System.Web.Http namespace. You can install it via NuGet with Install-Package Microsoft.AspNet.WebApi.Core.

This post has been heavily inspired by this blog post.

Dayan
  • 7,634
  • 11
  • 49
  • 76
Mihai Dinculescu
  • 19,743
  • 8
  • 55
  • 70
  • 3
    Sorry for the question, but is it common practice to ask a question and give prepared answer immediately? – Artiom Sep 08 '15 at 14:59
  • 4
    Yes. You can read more about it here: https://blog.stackexchange.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/. – Mihai Dinculescu Sep 08 '15 at 15:36
  • @Mihai-AndreiDinculescu Does that still work if there are more than 1 tokens on the page? There may be more than 1 form, meaning more than 1 hidden input with the token. – onefootswill May 21 '16 at 23:55
  • @onefootswill Yes, it should work. The token is independent of forms, as it is injected in the request headers. – Mihai Dinculescu May 23 '16 at 12:20
  • @Mihai-AndreiDinculescu But if there are more than one token on the page, then `angular.element('input[name="__RequestVerificationToken"]')` would return a set of more than one elements. – onefootswill May 23 '16 at 13:12
  • 1
    @onefootswill Yes, obviously. But why would you have multiple tokens on one page? One should serve all requests. You don't need to include it in your form. Have it at the start or the end of the body tag's content. – Mihai Dinculescu May 23 '16 at 13:34
  • @Mihai-AndreiDinculescu You might have multiple tokens if you had multiple forms on the 1 page. 1 token for each form. – onefootswill May 23 '16 at 22:55
  • 2
    @onefootswill If you have multiple forms you should only generate the token once. It should serve that *request* not an individual form. – Phillip Copley Jun 23 '16 at 18:30
  • @Mihai-AndreiDinculescu My understanding is that `CSRF` is possible if the application use `cookies` as mode for user authentication, since, `cookies` are automatically send by the browser. What if application uses `OAUTH` token to authenticate user. Does `AntiForgeryToken` validation still necessary? Because, neither `browser` nor `attacker` have access to the token and `token` is `explicitly` sent by `application` for each `request`. – Ravi Teja Oct 07 '16 at 05:30
  • @RaviTeja *OAUTH* **needs** *CSRF* protection. But it is done a bit differently, by using the `state` parameter. Read http://www.twobotechnologies.com/blog/2014/02/importance-of-state-in-oauth2.html and search for other articles/tutorials that might be relevant. – Mihai Dinculescu Oct 11 '16 at 10:14
0

Add __RequestVerificationToken to FormData

 var formData = new FormData();
    formData.append("__RequestVerificationToken", token);
    formData.append("UserName", $scope.kullaniciAdi);
    formData.append("Password", $scope.sifre);

    $http({
        method: 'POST',
        url: '/Login/Login',
        data: formData,
        transformRequest: angular.identity, 
        headers: { 'Content-Type': undefined }

    }).then(function successCallback(response) {
      


    }, function errorCallback(response) {

    });
Leandro Bardelli
  • 10,561
  • 15
  • 79
  • 116
Ufuk Aydın
  • 158
  • 3
-1

Add to the ASP.NET MVC View

<Form ng-submit="SubmitForm(FormDataObject)">
        @Html.AntiForgeryToken()
        .....
        ...
        .
</Form>

Then at AngularJs controller

angular.module('myApp', []).controller('myController', function ($scope, $http, $httpParamSerializerJQLike) {

        $scope.antiForgeryToken = angular.element('input[name="__RequestVerificationToken"]').attr('value');

        $scope.SubmitForm = function (formData) {
            var dataRequest = {
                __RequestVerificationToken: $scope.antiForgeryToken,
                formData: angular.toJson(formData)
            };

            $http.post("/url/...", $httpParamSerializerJQLike(dataRequest), { headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } }).then(function (response) {
                $scope.result = JSON.parse(response.data);
            });
        }
    });

Why $httpParamSerializerJQLike(dataRequest)? Because without that AngularJs serialize data as:

{__RequestVerificationToken: blablabla, formData: blablabla}

and Asp.NET MVC controller throws The required anti-forgery form field “__RequestVerificationToken” is not present error.

But if you serialize the request data with $httpParamSerializerJQLike(dataRequest), AngularJs serialize as:

__RequestVerificationToken: blablabla
formData: blablabla

and Asp.NET MVC controller can recognize the Token without any error.