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

5

24

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' }   
  });
Impostor answered 20/8, 2013 at 13:45 Comment(0)
Q
29

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.

Quaint answered 20/8, 2013 at 15:9 Comment(2)
Hello , I added script in the head. I injected CSRF_TOKEN . How do I add it inside form ? I edited my code btw.Impostor
I still could not manage it , I read the code end-to-end. I edited $scope.addItem . Can you give a look ?Impostor
V
23

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.

Vampire answered 20/5, 2014 at 12:50 Comment(7)
Hey I added one more alternative for that solution. Can you check and make your comment if it is a nice solution also ?Impostor
@Impostor - I don't understand how specifying the content-type in the header is an alternative solution for getting a CSRF token from the server to the AngularJS app.Vampire
I followed this tutorial scotch.io/tutorials/php/…Impostor
@Vampire are you serving your angular app from the same domain? I currently have my api at api.example.com and angular being served from example.com so I am having problems with the session being made since it is considered cross origin.Tee
@Tee - Sorry for late reply - I've been on holiday. You should be able to do this by setting the domain of your javascript app to the same as your main web application e.g. for example.com, - document.domain = 'example.com';.Vampire
This doesn't work for a REST api, the token is not going to be the same.Hulking
@Vampire Do I need to create the filter.php file also in Laravel 5 or I can use come inbuilt library class to achieve this? If i have to add it then what would be the location? app/ in L5 are the DB model classes,so I dont want something there.Please suggest.Kehr
B
23

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

Beautifully answered 17/6, 2015 at 12:54 Comment(1)
awesome. completely missed thisDraughts
C
7

I think my solution is less pain and much more flexible, especially it thinks testing your App on Karma.

Firstly add this code your master view

 <meta name="csrf-token" content="{{ csrf_token() }}">

We have saved csrf token into html content without adding route.

Now we protect all requests of AngularJs App by CSRF token

/**
 * 
 * when it thinks testing your app unit test with Karma,
 * this solution was better than getting token via AJAX.
 * Because low-level Ajax request correctly doesn't work on Karma
 * 
 * Helper idea to me :
 * https://mcmap.net/q/162731/-rails-csrf-protection-angular-js-protect_from_forgery-makes-me-to-log-out-on-post/15761835#15761835 
 * 
 */
var csrftoken =  (function() {
    // not need Jquery for doing that
    var metas = window.document.getElementsByTagName('meta');

    // finding one has csrf token 
    for(var i=0 ; i < metas.length ; i++) {

        if ( metas[i].name === "csrf-token") {

            return  metas[i].content;       
        }
    }  

})();

// adding constant into our app

yourAngularApp.constant('CSRF_TOKEN', csrftoken); 

We need to setup default http headers for Angular. Let's add our csrf token to Angular's headers

/*
 * App Configs
 */
blog.config(['$httpProvider', 'CSRF_TOKEN',

  function($httpProvider, CSRF_TOKEN) {


    /**
     * adds CSRF token to header
     */
    $httpProvider.defaults.headers.common['X-CSRF-TOKEN'] = CSRF_TOKEN;

 }]);

Finally we have to need new filter for this changes on side of laravel..

Route::filter('csrfInHeader', function($route, $request) {

    if (Session::token() !== (string) $request->header('X-CSRF-TOKEN') ) {

        throw new Illuminate\Session\TokenMismatchException;

    }
});

"csrfInHeader" filter will check all http request by angular app. You are not need adding csrf token to every each request. Plus if you test your app by Karma, you will not effort to getting csrf token on testing..

Chaffee answered 17/1, 2015 at 11:38 Comment(0)
G
1

The easiest way to do it as

Route::get('/getToken','Controller@getToken');

In your web or api.php file

In Controller

public function getToken(){

          return csrf_token();

    }

Place this code

In Angular app

$http.get("http://localhost:8000/getToken")
  .then(function(response) {
    alert(response.data)
  });

Safe way to get csrf_token()

Gallia answered 11/5, 2019 at 23:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.