6

I'd like to pass a RequestVerificationToken which is generated by a Razor MVC helper in my login form to an AngularJS service that I've done to manage the authentication of my application

my form is the following:

<div ng-model="loginRequest" >
        <form ng-submit="submit()"  ng-controller="loginCtrl">
            @Html.AntiForgeryToken()

            <input id="username" ng-model="loginRequest.Username"  type="text" name="text"  />
            <input id="password" ng-model="loginRequest.Password" type="text" name="text" />
            <input type="submit" id="submit" value="Submit" />
            <br/>
            isValid: {{loginRequest.isValid}}
            <br/>
            username: {{loginRequest.Username}}
            <br/>
            Password: {{loginRequest.Password}}
        </form>
    </div>

the @Html.AntiForgeryToken() renders in this way:

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

my AngujarJs controller injects successfully my "loginService" and I can send via Post the username and the password to the service

function loginCtrl($scope, loginService) {
   $scope.submit = function () {
      loginService.authenticate($scope.loginRequest,function(data) {
          $scope.loginRequest.isValid = (data.User!=null);
          //console.log(data);
      });

   };
}

the service:

angular.module('App.services', ['ngResource']).
    factory('loginService',
        function ($resource) {
            return $resource('/Api/User/login', '',
                {
                        authenticate: {
                        method: 'POST',
                        isArray: false,
                        headers: { 'X-XSRF-Token': '?????' }
                    }
                });
        });

my question is how can I read the token rendered in the form and pass it to the service and set a header with the token taken from the login form, as far I as Know is not a good practice to manipulate the DOM and I don't know if I'll need to create a directive to perform that task so any suggestions are welcome!

pedrommuller
  • 15,741
  • 10
  • 76
  • 126
  • Hope you have looked at http://www.infoq.com/news/2012/10/anti-forgery-aspnet-json and http://stackoverflow.com/questions/15574486/angular-against-asp-net-webapi-implement-csrf-on-the-server – Chandermani Aug 07 '13 at 05:12
  • yes I did, I´m using servicestack for it I just got everything covered I just need to know how to pass the parameter to the service! in fact I think I'm following a very similar approach like the one described here http://stackoverflow.com/questions/15444781/angularjs-cant-find-xsrf-token-cookie – pedrommuller Aug 07 '13 at 05:22
  • did my answer help you at all? – Jason More Aug 08 '13 at 14:53

3 Answers3

14

I think I've found a pretty good solution. I started with the suggestions here http://www.novanet.no/no/blog/olav-nybo/dates/2013/12/anti-forgery-tokens-using-mvc-web-api-and-angularjs/ but the problem was that the directive is executed after the controller. So if you wanted to retrieve initial data in the controller it was very difficult.

Instead of using the directive just use the Module.run() method as referenced in the $http service documentation under the Setting HTTP Headers section http://docs.angularjs.org/api/ng.$http.

I used the HtmlHelper extension referenced in the blog post above with the body element and then my Module.run() looks like this:

myModule.run(['$http', function($http) {
    $http.defaults.headers.common['RequestVerificationToken'] = angular.element("body").attr('ncg-request-verification-token');
}]);

I think it makes for a pretty elegant solution.

Josh Russo
  • 3,080
  • 2
  • 41
  • 62
3

sadly, the easiest way is to include jquery.js into your project before angular.js, then do this:

headers: { 'X-XSRF-Token': angular.element('input[name="__RequestVerificationToken"]') }
Jason More
  • 6,983
  • 6
  • 43
  • 52
  • hey @Jason More, I just would like to know a way without using JQuery it supposed that not recommended, I'm learning angularJS right now so I'd like to start with the right foot since the beginning! – pedrommuller Aug 08 '13 at 16:07
  • 1
    Unfortunately you are mixing two things - Server side rendering (razor) and client side templating (angular). jQuery is the glue between them. I'll ask around but after thinking on it more, I believe this is the correct way – Jason More Aug 08 '13 at 16:45
  • 1
    @Pedro You will have to do something to get it out of the DOM... you could add the bit of code Jason posted in to an Angular service/provider/factory and then inject that in to your login service, but really that is just adding a layer of abstraction which may make it easier to test. – DigitalZebra Aug 08 '13 at 17:15
  • @Polaris878 yeah I'll keep that in mind thanks guys! I just need to try and Idea I'll keep you posted! – pedrommuller Aug 08 '13 at 17:22
  • Guys, what could be the case? if I modify the way that @Html.AntiForgeryToken() renders and add an overload that accepts the name for the ng-model, I think that changes the whole picture right? – pedrommuller Aug 08 '13 at 17:34
  • @Pedro if you can change the output, thats even better. Ideally you would make a directive that matches the output of whatever the AnitForgeryToken spits out, that passes the token into the service, which is then later consumed by $resource. But being able to use ng-model will at least let you send it to a controller. – Jason More Aug 08 '13 at 17:36
  • @Pedro you probably would want to use 'ng-init' for this task: http://code.angularjs.org/1.1.5/docs/api/ng.directive:ngInit – Jason More Aug 08 '13 at 17:38
  • @JasonMore I try to get it using angular.element and I get a weird message on when I click the submit button "Accessing selectionDirection on an input element that cannot have a selection." – pedrommuller Aug 10 '13 at 19:21
  • @Pedro googling appears to point out it is the AngularJS pluggin for chrome dev tools: https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk Try disabling it and see if the message goes away. – Jason More Aug 12 '13 at 12:50
  • @JasonMore I googled it too, the batarang is disabled I ended up to move the $resorce to the controller since the services are the first to load whatever it happens the value always will be null and because they are singletons it only loads one time so i figure it out that putting that method as a service isn't the best solution, at least for me, I'll update the post, but I really appreciate your help, the nd-init did some part of the trick! thanks! a lot! – pedrommuller Aug 12 '13 at 13:50
0

I had this same problem and I solved it making a custom AntiForgeryToken method in an custom HtmlHelper.

public static IHtmlString AngularAntiForgeryToken(this HtmlHelper html, string ngModelName = "AntiForgeryToken")
{
    MvcHtmlString antiForgery = html.AntiForgeryToken();
    string antiForgeryString = antiForgery.ToString();
    Regex regex = new Regex(string.Format("value=[\"|']([a-zA-Z0-9+=/\\-_]+)[\"|']", ngModelName));
    Match match = regex.Match(antiForgeryString);
    string antiForgeryToken = string.Empty;
    if (match.Success)
        antiForgeryToken = match.Groups[1].ToString();

    string result = antiForgeryString.Replace("<input", string.Format("<input ng-model=\"{0}\" ng-init=\"{0} = '{1}'\"", ngModelName, antiForgeryToken));

    return new HtmlString(result);
}
Estefano Salazar
  • 419
  • 4
  • 15