CSRF token doesn't work (yii2) - angularjs

I'm trying to pass CSRF token with async http request.
Yii2 server response:
Bad Request (#400) Unable to verify your data submission.
Angular request:
$http.post(
/web/api/search',
{
'_csrf': yii.getCsrfToken(),
'data': $scope.data
}
).success(function(response) {
console.log("Hello, World!");
});
CSRF meta tags enabled as well: <?= Html::csrfMetaTags() ?>.
What is wrong?

Try this initialization code in Angular. This code run correctly CSRF validation:
var moduleName = 'app';
var app = angular.module(moduleName, []);
app.run( function run($http){
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
$http.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr("content");
});

Related

Add Firebase Token to Angular $HTTP header

So I want to add a firebase token to all http requests. I am using the angularJS 1.X framework.
I have an interceptor function which normally I would just add it like this:
.factory('Interceptor', function () {
interceptorFactory.request = function (config) {
config.token = firebaseToken;
return config;
};
return interceptorFactory;
})
.config(function ($httpProvider) {
$httpProvider.interceptors.push('Interceptor');
});
But the issue is the only way to get the firebase token is through a promise, so for that I would need to do something like this:
.factory('Interceptor', function () {
interceptorFactory.request = function (config) {
getFirebaseToken().then(function(token){
config.token = token;
return config;
});
};
return interceptorFactory;
})
.config(function ($httpProvider) {
$httpProvider.interceptors.push('Interceptor');
});
The problem though, is because the token is retrieved Asyncronously, the factory interceptor does not wait for promise to resolve, so the request is sent without the token. If anyone knows how to get around this that would be help full.
Also, I know I could just get the token before I make every http request but then that would cause a lot of duplicate code, and I can't get the token on application startup and just store it cause it has an expiration.

How to recieve csrf token from laravel rotues to angularjs meta tag [duplicate]

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' }
});
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.
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.
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
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 :
* http://stackoverflow.com/questions/14734243/rails-csrf-protection-angular-js-protect-from-forgery-makes-me-to-log-out-on/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..
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()

Angularjs $http service passing facebook access token

I'm implementing fb authentication in my SPA built using MEAN stack. While I've successfully implemented the fb authentication using facebook token passport strategy, I'm facing issues in securing API endpoints. Because for that I need to pass both the authenticated user object and access token in the $http service and I've tried passing access_token as a property of the user object and also as a header property, but I still 401 (Unauthorized error). Below is my code snippet.
Passport documentation says "Authorization: Bearer base64_access_token_string". Should the token be encoded in a base64 format? Pls help.
server code
app.get('/api/getbikes*',
passport.authenticate('facebook-token',{session: false}),
function(req,res){
if(req.user){
console.log('In getbikes api');
// console.log('req.query :',req.query);
var msg="";
ubBike
.find({cust:req.query._id})
.populate('cust','email')
.exec(function(err,bikes){
res.send(bikes);
if(err) throw err;
});
}
else
{
res.send(401);
}
});
angular code
service
this.getbikes = function(user){
var deferred = $q.defer();
$http({
method:"GET",
url:"http://localhost:3000/api/getbikes",
params: user,
headers:{
Authorization:auth.getAccesstoken()
}
}).then(function successCallback(srresponse){
deferred.resolve(srresponse.data);
},
function failureCallback(srresponse){
$log.error("get bikes http call failed ",srresponse.data);
deferred.reject(srresponse.data);
});//$http
return deferred.promise;
};//getbikes
controller
$scope.fblogin= function(){
auth.fblogin().then(
function(response){
$scope.isAuth = auth.isAuth;
$scope.usr =auth.getResponseobj();
$scope.usr.access_token=auth.getAccesstoken();
$scope.profpic=auth.profpic;
bike.getbikes($scope.usr).then(function(response){
if (response.length ==0)
{
$location.path('/addbike');//redirect to addbike screen
}
else{
$location.path('/appoint');//else redirect to view appointment screen
}
},function(reason){
$scope.msg1 = reason;
});//getbikes
},function(reason){
$log.log("fblogin() - failure :Need to login to the application :"+reason);
})
};//fblogin
Surprisingly, when I send the header as "Authorization: Bearer access_token_string" ie the fb token as is without base64 encoding the API authentication works perfectly fine. This is contrary to the passport facebook token documentation https://github.com/drudge/passport-facebook-token

XMLHttpRequest cannot load .... Response for preflight has invalid HTTP status code 401

Below is my service call where I am trying to do a basic auth. I have checked multiple blogs could not find the solution for this.
Can anybody help me to solve this issue as I am getting below error:
XMLHttpRequest cannot load
Response for preflight has invalid HTTP status code 401
I could not find the basic auth in the network tab in developer options also.
function() {
"use strict";
var APIservice = function($http, $base64) {
var getDetails = function(postData) {
$http.defaults.headers.common['Access-Control-Allow-Origin'] = "*";
$http.defaults.headers.common['Access-Control-Allow-Methods'] = "GET,PUT,POST,DELETE,OPTIONS";
$http.defaults.headers.common['Access-Control-Allow-Headers'] = "Content-Type, Authorization, Content-Length, X-Requested-With";
$http.defaults.headers.common['Content-Type'] = undefined;
console.log($http.defaults.headers.common.Authorization);
//console.log($http.defaults.headers.common.Authorization);
return $http.get('http://52.74.68.202:8080/rest/v1/step/all')
.then(function(response, headers, config) {
console.log(response);
return response;
});
};
return {
getDetails: getDetails
}
}
var module = angular.module('expframework');
module.factory("APIservice", APIservice);
module.run(['$http', '$base64', function($http, $base64) {
var authdata = $base64.encode('test:test');
$http.defaults.headers.common['Authorization'] = 'Basic ' + authdata;
}]);
module.config(['$httpProvider', '$base64', function($httpProvider, $base64) {
var authdata = $base64.encode('test:test');
$httpProvider.defaults.headers.common['Authorization'] = 'Basic ' + authdata;
}])
}();
It is working in Safari and emulators but not working in Chrome and Firefox
Please help me to fix this. Thanks in advance.
Since your server threw a 401, I guess it tried to authenticate the preflight request. From https://stackoverflow.com/a/15734032/1225328:
The W3 spec for CORS preflight requests clearly states that user credentials should be excluded.
[...]
Simply have the server (API in this example) respond to OPTIONS requests without requiring authentication.

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' }
});
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.
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.
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
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 :
* http://stackoverflow.com/questions/14734243/rails-csrf-protection-angular-js-protect-from-forgery-makes-me-to-log-out-on/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..
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()

Resources