George Phillipson
George Phillipson

Reputation: 860

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

I'm unable to pass the RequestVerificationToken from webpage to server using AngularJs.

My AngularJs Code is:

var app = angular.module('validation', []);
app.controller('SignUpController', function ($scope, $http) {
    $scope.model = {};
    $scope.email = {};
    $scope.sendEmail = function () {
        $http({
            method: 'POST',
            url: '/Contact/Test',
            data: $scope.email,
            headers: {
                'RequestVerificationToken': $scope.antiForgeryToken
            }
        }).success();
    };
});

Custom Attribute Code:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public class CustomAntiForgeryTokenAttribute : 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");
            }
        }
    }

Form is:

@functions{
    public string GetAntiForgeryToken()
    {
        string cookieToken, formToken;
        AntiForgery.GetTokens(null, out cookieToken, out formToken);
        return cookieToken + ":" + formToken;
    }
}
<div ng-app="validation" ng-controller="SignUpController">
    <form role="form" id="frmContact" action="@Url.Action("Index", "Contact")" method="POST">
        <input id="antiForgeryToken" ng-model="antiForgeryToken" type="hidden" ng-init="antiForgeryToken='@GetAntiForgeryToken()'" />
        <fieldset class="form-group">
            @Html.LabelFor(x => x.EmailTitle)
            @Html.TextBoxFor(x => x.EmailTitle, new { placeholder = @Resource.EmailTitle, @class = "form-control", data_ng_model = "new.email.title" })
        </fieldset>
        <fieldset class="form-group">
            @Html.LabelFor(x => x.EmailAddress)
            @Html.TextBoxFor(x => x.EmailAddress, new { placeholder = @Resource.EmailAddress, @class = "form-control", data_ng_model = "new.email.address" })
        </fieldset>
        <fieldset class="form-group">
            @Html.LabelFor(x => x.EmailMessage)
            @Html.TextAreaFor(x => x.EmailMessage, new { placeholder = @Resource.EmailMessage, @class = "form-control", data_ng_model = "new.email.message" })
        </fieldset>


        <div>
            <button type="submit" name="btnEmailForm" id="btnEmailForm" class="btnLogin" ng-click="sendEmail()" value="sendMessage">@Resource.ContactFormSendMessageButton</button>
        </div>
        <div id="errorMessages" class="error">{{message}}</div>
    </form>
</div>

I have read the following posts, but cannot seem to solve the problem, and also took code from https://github.com/techbrij/angularjs-asp-net-mvc which works in that example but not in my MVC application:

http://techbrij.com/angularjs-antiforgerytoken-asp-net-mvc

https://parthivpandya.wordpress.com/2013/11/25/angularjs-and-antiforgerytoken-in-asp-net-mvc/

AngularJS Web Api AntiForgeryToken CSRF

http://bartwullems.blogspot.co.uk/2014/10/angularjs-and-aspnet-mvc-isajaxrequest.html

Where exactly to put the antiforgeryToken

http://www.ojdevelops.com/2016/01/using-antiforgerytokens-in-aspnet-mvc.html

Can anyone help with this problem

Upvotes: 0

Views: 7888

Answers (2)

jitin14
jitin14

Reputation: 142

Important: Default behavior of [ValidateAntiForgeryToken] expects __RequestVerificationToken token in form values. To send request to server in form values format one requires content-type to be set to application/x-www-form-urlencoded. But I didn't have this option unfortunately and my content-type was application/json. Hence I took this custom path.

Let me explain the approach I took which worked.

Step 1: Declare @Html.AntiForgeryToken() in your view (.cshtml) as shown below:

<form id="inputForm" name="inputForm" ng-submit="submit(broker)" novalidate>
        @Html.AntiForgeryToken()
        /* other controls of form */
</form>

Step 2: @Html.AntiForgeryToken() will render an hidden field which will hold the token value as:

<input name="__RequestVerificationToken" type="hidden" value="GvTcz2tTgHOS2KK_7jpHvPWEJPcbJmHIpSAlxY1">

Step 3: Create custom attribute for Anti-forgery token verification as

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]    
public class HbValidateAntiForgeryToken : FilterAttribute, IAuthorizationFilter, IExceptionFilter    
{    
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        try
        {
            var antiForgeryCookie = filterContext.HttpContext.Request.Cookies[AntiForgeryConfig.CookieName];
            AntiForgery.Validate(antiForgeryCookie != null ? antiForgeryCookie.Value : null,
                filterContext.HttpContext.Request.Headers["__RequestVerificationToken"]);                
        }
        catch (Exception ex)
        {                
            throw new SecurityException("Unauthorised access detected and blocked");
        }
    }

    public void OnException(ExceptionContext filterContext)
    {
        if (filterContext.Exception != null &&
            filterContext.Exception is System.Security.SecurityException)
        {
            filterContext.Result = new HttpUnauthorizedResult();
            // Handle error page scenario here
        }
    }
}

Step 4: Declare the above attribute wherever required (Only on HttpPost methods of controller. DO NOT declare on HttpGet)

[HttpPost]
[HbValidateAntiForgeryToken]
public JsonResult IsUsernameExists(string username)
{
}

Step 5: In AngularJS, in factory pass __RequestVerificationToken as header.

hbServices.factory('RegistrationService', ['$resource',
function ($resource) {
    return $resource(applicationPath + 'api/MyUserMembership/:dest', {}, {
        createNewUser: { method: 'POST', isArray: false, params: { dest: 'CreateNewUser' }, 
                         headers: { 
                             '__RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val()
                            }
                        },
        isUsernameExists: { method: 'POST', isArray: false, params: { dest: 'IsUsernameExists' }, 
        headers: { 
            '__RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val()
           }
       }
    });
}]);

Please note the way I am passing value of __RequestVerificationToken read from hidden field that was rendered by ASP.NET MVC's @Html.AntiForgeryToken().

My application was using jquery and has reference of jquery already so reading the value was easy. You can try other methods to read this value

Summary The AntiForgery.Validate() does the magic of validating the value of forgery token here and is wonderful so far. Hope this helps!

Upvotes: 1

Slava Utesinov
Slava Utesinov

Reputation: 13498

At this case you perform form submit and $scope.sendEmail operations and they may conflict one with another, to prevent this behavior you can use ng-submit directive. And also add attributes: name= '__RequestVerificationToken' and ng-value="antiForgeryToken" to corresponding input.

Upvotes: 1

Related Questions