Sep 18, 2013

AngularJS and AntiForgeryToken in ASP.NET MVC

In my previous article, we have implemented form validation including username availability Check with AngularJS. Now we will use ASP.NET MVC’s AntiForgeryToken to prevent Cross-Site Request Forgery (CSRF) Attacks. It generates a hidden form field (anti-forgery token) that is validated when the form is submitted. I see some tutorials, but I want to do in AngularJS way.

Lets play with the previous code and add ValidateAntiForgeryToken attribute in SignUp post action in AccountController.


   [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public JsonResult SignUp(RegisterModel model)
        {

You will get error:

The required anti-forgery form field "__RequestVerificationToken" is not present.

which is expected.

1. View Changes:

We use AntiForgery.GetTokens method to generate the token like below:


@functions{
    public string GetAntiForgeryToken()
    {
        string cookieToken, formToken;
        AntiForgery.GetTokens(null, out cookieToken, out formToken);
        return cookieToken + ":" + formToken;                
    }
}

Create a hidden field in form, define ngModel and use above method to init model value.


  <form name="mainForm" data-ng-submit="sendForm()" data-ng-controller="SignUpController" novalidate>       
            <input id="antiForgeryToken" data-ng-model="antiForgeryToken" type="hidden" data-ng-init="antiForgeryToken='@GetAntiForgeryToken()'"  />        
       

Note:

data-ng-model="antiForgeryToken": to define model

data-ng-init="antiForgeryToken='@GetAntiForgeryToken()'": to initialize model value. In AngularJS, ngModel ignores hidden field but the value will be same on the page so use it.

2. AngularJS Controller Changes:

Add 'RequestVerificationToken': $scope.antiForgeryToken in headers. The controller code looks like:


app.controller('SignUpController', function ($scope,$http) {
    $scope.person = {};
    $scope.sendForm = function () {
        $http({
            method: 'POST',
            url: '/Account/SignUp',
            data: $scope.person,
            headers: {
                'RequestVerificationToken':  $scope.antiForgeryToken 
            }           
        }).success(function (data, status, headers, config) {
            $scope.message = '';
            if (data.success == false) {
                var str = '';
                for (var error in data.errors) {
                   str += data.errors[error] + '\n';
                }
                $scope.message = str;
            }
            else {
                $scope.message = 'Saved Successfully';               
                $scope.person = {};
            }
        }).error(function (data, status, headers, config) {                      
            $scope.message = 'Unexpected Error';
        });
    };    
});

If you run code at this moment, you will get error

The required anti-forgery cookie "__RequestVerificationToken" is not present.

3. Creating Attribute Class:

We will process the request, extract the tokens from the request header. Then call the AntiForgery.Validate method to validate the tokens. The Validate method throws an exception if the tokens are not valid. We create a custom attribute for this. Right click on Filters folder and add new class "MyValidateAntiForgeryTokenAttribute.cs"


  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public class MyValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
    {     
       

        private void ValidateRequestHeader(HttpRequestBase request)
        {
            string cookieToken = String.Empty;
            string formToken = String.Empty;
            string tokenValue = request.Headers["RequestVerificationToken"];           
                if (!String.IsNullOrEmpty(tokenValue))
                {
                    string[] tokens = tokenValue.Split(':');
                    if (tokens.Length == 2)
                    {
                        cookieToken = tokens[0].Trim();
                        formToken = tokens[1].Trim();
                    }
                }
            AntiForgery.Validate(cookieToken, formToken);
        }

        public void OnAuthorization(AuthorizationContext filterContext)
        {
           
            try
            {
                if (filterContext.HttpContext.Request.IsAjaxRequest())
                {
                    ValidateRequestHeader(filterContext.HttpContext.Request);
                }
                else
                {
                    AntiForgery.Validate();
                }
            }
            catch (HttpAntiForgeryException e)
            {
                throw new HttpAntiForgeryException("Anti forgery token cookie not found");
            }
        }
    } 

4. Use the above attribute in SignUp post action


   [HttpPost]
        [AllowAnonymous]
        [MyValidateAntiForgeryToken]
        public JsonResult SignUp(RegisterModel model)
        {

that's it.

Conclusion:

We have seen how to use the power of ASP.NET MVC’s AntiForgeryToken in AngularJS way. It is very useful when we are working in architecture way where we can't access value of controls in Ajax request directly.

Enjoy Programming !!