Azure API call: response contains html page - angularjs

I created an API app that is hosted in Azure and protected by the Active Directory (when I open the url in a browser, I get redirected to a login page from microsoft).
Now I have an angular app that should get the data from the api. I protected the angular app with the Actice Directory, too. In this manner I want to get a token which I can use for authentication.
API call:
const auth = btoa("[my username]:[my password]");
var token;
if((window.location.href).indexOf('/') != -1) {
var queryString = (window.location.href).substr((window.location.href).indexOf('/') + 1);
token = (queryString.split('='))[1];
token = decodeURIComponent(token);
}
$http.defaults.headers.common['Authorization'] = 'Basic ' + auth;
$http.defaults.useXDomain = true;
$http.defaults.headers.common['x-ms-token-aad-id-token'] = token;
delete $http.defaults.headers.common['X-Requested-With'];
var erg = $http.get('http://[app name].azurewebsites.net/api/contacts')
.success(function (data, status, headers, config) {
$scope.loggedIn = "LOGGED IN";
$scope.responseData = data;
})
.error(function (data, status, header, config) {
console.log("ERROR");
});
I had to open Chrome with disabled 'same origin policy'.
The data that was returned:
<html><head><title>Working...</title></head>
<body>
<form method="POST" name="hiddenform" action="https://[app name].azurewebsites.net/.auth/login/aad/callback"><input type="hidden" name="id_token" value="eyJ0[...]NBw" />
<input type="hidden" name="state" value="/api/contacts#" />
<input type="hidden" name="session_state" value="fdbe7ee3-[...]-b12663d39846" />
<noscript><p>Script is disabled. Click Submit to continue.</p><input type="submit" value="Submit" /></noscript>
</form>
<script language="javascript">document.forms[0].submit();</script>
</body>
</html>
How can I get the JSON data that the API should actually return? What can I do with this html snippet?

You can follow the sample in https://github.com/AzureAD/azure-activedirectory-library-for-js to integrate adal in your SPA. The SDK has implemented the functionalities acquiring access token from AAD. And also will intercept the http requests in Angularjs and add the authentication header.
You can refer to http://www.cloudidentity.com/blog/2015/02/19/introducing-adal-js-v1/ & http://www.cloudidentity.com/blog/2014/10/28/adal-javascript-and-angularjs-deep-dive/ for further understanding of adal for js.
And to make sure your SPA can call your APIs in server. You can try following configurations in Angualrjs config section:
app.config(['$locationProvider', 'adalAuthenticationServiceProvider', '$httpProvider', function($locationProvider, adalAuthenticationServiceProvider, $httpProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
$locationProvider.html5Mode(true).hashPrefix('!');
var endpoints = {
"http://garycors.azurewebsites.net": "bce85948-9ea4-4738-b1e6-972f3d30f4da",
// "http://garycors.azurewebsites.net": "90e54701-ff93-414c-b7ee-c2d9b01417f3",
};
adalAuthenticationServiceProvider.init({
tenant: "<tenant_id>", // Optional by default, it sends common
clientId: "<client_id>",
cacheLocation: 'sessionStorage'
},
$httpProvider // pass http provider to inject request interceptor to attach tokens
);
$httpProvider.interceptors.push('httpRequestInterceptor');
}]);
app.factory('httpRequestInterceptor', function () {
return {
request: function (config) {
config.headers['Authorization'] = 'Bearer ' + sessionStorage.getItem("adal.idtoken");
return config;
}
};
});
Any further concern, please feel free to let me know.

Related

Get session timeout from Coldfusion server in AngularJS

I have an application implemented in Coldfusion and AngularJS.
I have an external authentication system used for the security of our applications. It's called in the file application.cfc of the application. When the user tries to access to the application, the authentication system is called and if the authentication is correct, the user can access to the application. I put in session some data about the users'roles.
This security system is defined with another url.
In views I have some forms with drop-down list populating thanks to AJAX queries.
As there is no refresh of the page (Single page application).
If the server session is out, the queries are not correctly executed: I obtain the server status 302, because the authentication tries to be launched and I can see in the colnsole:
Failed to load https://myAuthenticationSystem: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://myApplication' is therefore not allowed access.
I try to retrieve the status in my Angular JS script and I obtain the status 0 and data are null. Is it normal ??
I would like to fore the reload of the aaplication if the server session is out, for avoiding this kind of problem (ideally with a pop-up message in AngularJS "Session Expired. You are going redirected to the authentication..."). How can I do that?
Here my code:
application.cfc
<cfcomponent output="false" extends="RootApplication">
<cfset this.name = "myApp"/>
<cfset this.SessionManagement="yes"/>
<cfset this.clientStorage="registry"/>
<cfset this.clientManagement="yes"/>
<cfset this.SetDomainCookies = false/>
<cfset This.loginStorage="session">
<cfset This.sessionTimeout=CreateTimeSpan(0,1,0,0)>
<cfset This.applicationTimeout = CreateTimeSpan(0,0,2,0) >
<cffunction name="OnApplicationStart" access="public" returntype="boolean" output="false" hint="Fires when the application is first created.">
<cfreturn true />
</cffunction>
<cffunction name="onSessionStart">
// CALL THE AUTHENTICATION SYSTEM
..........................
</cffunction>
<cffunction name="onRequestStart">
// Check the user access
...........................
// PUT DATA IN SESSION
<cfset SESSION.userLogin = "#authenticationSystem.USERID#">
<cfset SESSION.tocken = "#authenticationSystem.tocken#" />
<cfset SESSION.userRoles = #userProfile.Roles#>
</cffunction>
</cfcomponent>
index.cfm
<cfheader name="Access-Control-Allow-Origin" value="*">
<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org" ng-app="ContactsApp" class="ng-app:ContactsApp" id="ng-app">
<head>
............................
</head>
<body>
// TRANSFORM THE COLDFUSION VARIABLES IN JS VARIABLES FOR ANGULAR JS
<script type="text/javascript" language="JavaScript">
<cfoutput>
var #toScript(SESSION.userLogin, "userLogin")#;
var #toScript(SESSION.tocken, "tocken")#;
var #toScript(SESSION.userRoles, "userRoles")#;
</cfoutput>
</script>
<div>
<ng-view></ng-view>
</div>
............................
</body>
</html>
app.js
var app=angular.module('ContactsApp', ['ngRoute', 'ui.bootstrap', 'ngDialog', 'angular-popover']);
// register the interceptor as a service
app.factory('HttpInterceptor', ['$q', '$rootScope', function($q, $rootScope) {
return {
// On request success
request : function(config) {
// Return the config or wrap it in a promise if blank.
return config || $q.when(config);
},
// On request failure
requestError : function(rejection) {
//console.log(rejection); // Contains the data about the error on the request.
// Return the promise rejection.
return $q.reject(rejection);
},
// On response success
response : function(response) {
//console.log(response); // Contains the data from the response.
// Return the response or promise.
return response || $q.when(response);
},
// On response failure
responseError : function(rejection) {
//console.log(rejection); // Contains the data about the error.
//Check whether the intercept param is set in the config array.
//If the intercept param is missing or set to true, we display a modal containing the error
if (typeof rejection.config.intercept === 'undefined' || rejection.config.intercept)
{
//emitting an event to draw a modal using angular bootstrap
$rootScope.$emit('errorModal', rejection.data);
}
// Return the promise rejection.
return $q.reject(rejection);
}
};
}]);
app.config(function($routeProvider, $httpProvider, ngDialogProvider){
$httpProvider.defaults.cache = false;
if (!$httpProvider.defaults.headers.get) {
$httpProvider.defaults.headers.get = {};
}
// disable IE ajax request caching
$httpProvider.defaults.headers.get['If-Modified-Since'] = '0';
// Add the interceptor to the $httpProvider to intercept http calls
$httpProvider.interceptors.push('HttpInterceptor');
$routeProvider.when('/all-contacts',
{
templateUrl: 'template/allContacts.html',
controller: 'ctrlContacts',
})
.when('/view-contacts/:contactId',
{
templateUrl: 'template/viewContact.html',
controller: 'ctrlViewContacts'
})
.otherwise({redirectTo:'/all-contacts'});
});
)
app.controller('ctrlContacts', function ($scope, $timeout, MyTextSearch, ContactService){
// ACCESS TO THE JS VARIABLES
$scope.userRoles = userRoles;
$scope.userLogin = userLogin;
............
});
app.controller('ctrlViewContacts', function ($scope, $routeParams, MyTextSearch, ContactService, RequestService, ReportService){
// ACCESS TO THE JS VARIABLES
$scope.userRoles = userRoles;
$scope.userLogin = userLogin;
// EXECUTE AJAX QUERY
ContactService.loadCategory('undefined',0).success(function(categories, status, header, config){
$scope.categories = categories;
console.log("status:" + status);
})
.error(function (data, status, header, config) {
console.log("sessionExpired: " + sessionExpired);
console.log("ERROR ");
console.log("data: " + data);
console.log("status:" + status);
console.log("header: " + header);
console.log("config: " + config);
if (status==302) {
alert("Session expired - New Authentication requested");
window.location.replace(headers('http://intragate.test.ec.europa.eu/remed'));
}
if (status==0) {
alert("ERROR 0 - Session expired - New Authentication requested");
//$window.location.reload();
}
}).finally(function() {
console.log("finally finished repos");
});
});
.........................
Thanks in advance for your help.
Sebastien

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()

Authorization header in AngularJS not working

I am using the Django REST token authentication for my API.
I posted my credentials to obtain token endpoint. However when I try to set the header in a correct way it keeps responding with a http 401 error. I tried it using curl -X GET http://127.0.0.1:8000/events/ -H 'Authorization: Token 4d92d36768ca5d555b59cf68899eceab39c23704 ' and that does work! This is my code:
app.controller('HomeController', ['$scope','$http', function($scope,$http) {
$scope.username = '';
$scope.password = '';
$scope.submitLogin = function () {
var credentials = {
username : $scope.username,
password : $scope.password,
};
var req = $http.post('http://127.0.0.1:8000/api-token-auth/', credentials);
req.success(function(data, status, headers, config) {
$scope.token = data.token;
var str1 = 'Token ';
$scope.tokenheader = str1.concat($scope.token);
$http.defaults.headers.common.Authorization = $scope.tokenheader;
});
req.error(function(data, status, headers, config) {
alert( "failure message: " + JSON.stringify({data: data}));
});
};
$scope.getEvents = function () {
var req = {
method: 'GET',
url: 'http://127.0.0.1:8000/events/',
}
$http(req).then(
function() {
console.log('succes')
},
function(){
console.log('fail')
});
};
}]);
And the error message in chrome dev tools:
XMLHttpRequest cannot load http://127.0.0.1:8000/events/.
Response for preflight has invalid HTTP status code 401
How do I get rid of this 401 error?
Edit: I just found out the fault lies in the fact that I did not have CORS installed on my API. I was using a CORS plugin in chrome that worked for the authentication part of my api but not for my events url!
Did you check that the token is actually added to your request?
You can do this for example using the Chrome developers tools.
Personally I prefer to use the $httpprovider.interceptor as described in:
angularjs $httpProvider interceptor documentation
This ensures that the tokens are always present on any call.
If you are accessing more than one API, you should consider adding something like:
$httpProvider.interceptors.push(['$q', '$location', '$log', 'loginService', 'restHelperService',
function ($q, $location, $log, loginService, restHelperService) {
return {
request: function (config) {
// check if the request comes with an url
if (config.url) {
// check that the call is to the REST api, if yes add token
if (restHelperService.isRestCall(config.url)) {
// add auth header or revert to login
if (loginService.userIsLoggedIn()) {
config.headers = config.headers || {};
config.headers.Authorization = 'Token ' + loginService.getToken().token;
} else {
$location.path('/login');
}
}
}
return config;
},
responseError: function (response) {
if (response.status === 401 || response.status === 403) {
// clear auth token if the REST call failed with the current token
if (response.config && response.config.url && restHelperService.isRestCall(response.config.url)) {
$log.debug(" restCall failed due to bad credentials, resetting credentials");
loginService.resetCredentials();
$location.path('/login');
}
}
return $q.reject(response);
}
};
}]);
}])
This avoid issues that will arise when you start adding the token to API calls that don't expect them. Also the code ensures that a user will be automatically redirected to the login page if the credentials are not valid.
The example, I'm using two additional services. A loginService that manages the tokens and a restHelperService that manages the urls of the REST framework.
I would recommend doing the same as else it will be hard to access the credentials from outside your controller.
You need to add Token to the headers:
get($http, "/some_url", {headers: {"Authorization": "Token " + $your_token}}
....
....
);
Response code 401 means Unauthorized. If you are using Token based authentication then in case of fail it would be 403, Forbidden.
So my guess would be that it's username/password who is messing with it. In your curl example you are not using them.

OAuth Token Response to Angular View (cookie)

I've been struggling with this for a couple hours now and need some help. I've created a simple app that presents the user with a "Login Using Google" button in an angular view that redirects the user to the Google Oauth page. Here's the controller code that calls the login() function when the button is pressed:
angular.module('dashApp').controller('SigninCtrl', function ($scope) {
$scope.login=function() {
var client_id="191641883719-5eu80vgnbci49dg3fk47grs85e0iaf9d.apps.googleusercontent.com";
var scope="email";
var redirect_uri="http://local.host:9000/api/auth/google";
var response_type="code";
var url="https://accounts.google.com/o/oauth2/auth?scope="+scope+"&client_id="+client_id+"&redirect_uri="+redirect_uri+
"&response_type="+response_type;
window.location.replace(url);
};
});
The redirect URI set in my google project redirects to a server page to this server page:
'use strict';
var _ = require('lodash');
var request = require('request');
var qs = require('querystring');
var fs = require('fs');
// Get list of auths
exports.google_get = function (req,res){
var code = req.query.code,
error = req.query.error;
if(code){
//make https post request to google for auth token
var token_request = qs.stringify({
grant_type: "authorization_code",
code: code,
client_id: "191641883719-5eu80vgnbci49dg3fk47grs85e0iaf9d.apps.googleusercontent.com",
client_secret: process.env.GOOGLE_SECRET,
redirect_uri: "http://local.host:9000/api/auth/google"
});
var request_length = token_request.length;
var headers = {
'Content-length': request_length,
'Content-type':'application/x-www-form-urlencoded'
};
var options = {
url:'https://www.googleapis.com/oauth2/v3/token',
method: 'POST',
headers: headers,
body:token_request
};
request.post(options,function(error, response, body){
if(error){
console.error(error);
}else{
//WHAT GOES HERE?
}
});
}
if(error){
res.status(403);
}
}
I'm able to exchange the code returned by google for an auth token object successfully and log it to the terminal. I've been told that I should set a cookie using:
res.setHeader('Content-Type', 'text/plain');
res.setCookie('SID','yes',{
domain:'local.host',
expires:0,
path:'/dashboard',
httpOnly:false
});
res.status(200);
res.end();
Followed by a controller on the page I'm directing the user to that validates the session.
What am I doing wrong?
Since you have already done the hard work so there is no point talking about passport.js which is actually written to simplify these kind of social login authentication.
So let's come directly to session implementaion logic.
You need to set the following header in your app.js/server.js :
app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type, Authorization');
next();
});
Let's say you are returning this token after successful login :
{
name : "some name",
role : "some role",
info : "some info"
}
You can have a client side function in your angular service or controller :
function(user,callback){
var loginResource = new LoginResource(); //Angular Resource
loginResource.email = user.email;
loginResource.password = user.password;
loginResource.$save(function(result){
if(typeof result !== 'undefined'){
if(result.type){
$localStorage.token = result.token;
$cookieStore.put('user',result.data);
$rootScope.currentUser = result.data;
}
}
callback(result);
});
}
LoginResource calls your REST endpoint which returns auth token.
You can store your auth token in localStorage and cookieStore.
localStorage makes sure that we are having the token saved even when user has closed the browser session.
If he clears the localStorage and cookieStorage both then log him out as you don't have any valid token to authorize user.
This is the same logic which i am using here. If you need more help then let me know.

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