Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to send csrf_token() inside AngularJS form using Laravel API?

I am trying to build an angular + laravel rest application. I can get the views of my database. When I try to add new items. I get 500 error telling me mismatch csrf token. My form layout is :

<form class="form-horizontal" ng-submit="addItem()">

  <input type="text" ng-model="itemEntry" placeholder="Type and hit Enter to add item">
</form>

This is how I try to add item to database :

$scope.addItem = function(CSRF_TOKEN) {
    $http.post('/shop', { text: $scope.itemEntry, csrf_token: CSRF_TOKEN} ).success(function(data, status) {
        if(data) {
            var last = _.last($scope.items);
            _token = CSRF_TOKEN;
            $scope.items.push({text: $scope.itemEntry, bought: false, id: (last.id + 1) });
            $scope.itemEntry = '';
            console.log($scope.items);
        } else {
            console.log('There was a problem. Status: ' + status + '; Data: ' + data);
        }
    }).error(function(data, status) {
            console.log('status: ' + status);
        });

}

Here is my filter that I use for my application:

Route::filter('csrf', function()
{
    if (Session::token() != Input::get('_token'))
    {
        throw new Illuminate\Session\TokenMismatchException;
    }
});

In my blade views I use this and it works :

<input type="hidden" name="_token" value="{{ csrf_token() }}" />

How can I send the csrf_token when I use html forms?

Thanks

Edit 1 : Adding header to post request like this does not give errors.

  $http({
    method  : 'POST',
    url     : '/shop',
    data    :  $scope.itemEntry,  // pass in data as strings
    headers : { 'Content-Type': 'application/x-www-form-urlencoded' }   
  });
like image 256
ytsejam Avatar asked Aug 20 '13 13:08

ytsejam


3 Answers

An option will be to inject the CSRF token as a constant. Append the following in your head tag:

<script>
  angular.module("app").constant("CSRF_TOKEN", '{{ csrf_token() }}');
</script>

Then in your module methods it can be injected when needed.

app.factory("FooService", function($http, CSRF_TOKEN) {
    console.log(CSRF_TOKEN);
};

Maybe you will be interested of peeking at the source code of this sample Laravel + AngularJS project.

like image 67
Rubens Mariuzzo Avatar answered Nov 10 '22 15:11

Rubens Mariuzzo


the accepted solution by Rubens Mariuzzo works, however I think that I have found an alternative solution which I think is better.

This way you don't have to pass data from the html script into your angularjs app and there is a better separation of concerns. E.g. This allows you to have your Laravel APP as just an API.

My solution involves getting the CSRF token via an api request and setting this value as a constant.

Further, instead of injecting the CSRF token when needed, you set the token in a default header which would get checked by the server upon any API http request.

Example shows laravel, however any serious framework should be able to offer something similar.

CSRF Route in LARAVEL:

// Returns the csrf token for the current visitor's session.
Route::get('api/csrf', function() {
    return Session::token();
});

Protecting Routes with the before => 'api.csrf' Filter

// Before making the declared routes available, run them through the api.csrf filter
Route::group(array('prefix' => 'api/v1', 'before' => 'api.csrf'), function() {
Route::resource('test1', 'Api\V1\Test1Controller');
Route::resource('test2', 'Api\V1\Test2Controller');
});

The api.csrf filter

// If the session token is not the same as the the request header X-Csrf-Token, then return a 400 error.
Route::filter('api.csrf', function($route, $request)
{
if (Session::token() != $request->header('X-Csrf-Token') )
{
    return Response::json('CSRF does not match', 400);
}
});

The AngularJS stuff put this in app.js:

Blocking Version:

var xhReq = new XMLHttpRequest();
xhReq.open("GET", "//" + window.location.hostname + "/api/csrf", false);
xhReq.send(null);

app.constant("CSRF_TOKEN", xhReq.responseText);

app.run(['$http', 'CSRF_TOKEN', function($http, CSRF_TOKEN) {    
    $http.defaults.headers.common['X-Csrf-Token'] = CSRF_TOKEN;
}]);

Non-Blocking Version

var xhReq = new XMLHttpRequest();
xhReq.open("GET", "//" + window.location.hostname + "/api/csrf", true);

xhReq.onload = function(e) {
  if (xhReq.readyState === 4) {
    if (xhReq.status === 200) {
      app.constant("CSRF_TOKEN", xhReq.responseText);

      app.run(['$http', 'CSRF_TOKEN', function($http, CSRF_TOKEN) {
        $http.defaults.headers.common['X-Csrf-Token'] = CSRF_TOKEN;
      }]);
    }
  }
};

xhReq.send(null);

Now the CSRF_TOKEN constant is injected as a header in ALL http requests from the AngularJS app and ALL API routes are protected.

like image 30
Gravy Avatar answered Nov 10 '22 15:11

Gravy


If you use Laravel 5, no need to add CSRF token to Angular http headers.

Laravel 5 with Angular do this automatically for you.

http://laravel.com/docs/5.1/routing#csrf-x-xsrf-token

like image 23
Modder Avatar answered Nov 10 '22 13:11

Modder