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()
Related
I have created a demo using JavaScript for Flickr photo search API.
Now I am converting it to the AngularJs.
I have searched on internet and found below configuration.
Configuration:
myApp.config(function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
});
Service:
myApp.service('dataService', function($http) {
delete $http.defaults.headers.common['X-Requested-With'];
this.flickrPhotoSearch = function() {
return $http({
method: 'GET',
url: 'http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=3f807259749363aaa29c76012fa93945&tags=india&format=json&callback=?',
dataType: 'jsonp',
headers: {'Authorization': 'Token token=xxxxYYYYZzzz'}
});
}
});
Controller:
myApp.controller('flickrController', function($scope, dataService) {
$scope.data = null;
dataService.flickrPhotoSearch().then(function(dataResponse) {
$scope.data = dataResponse;
console.log($scope.data);
});
});
But still I got the same error.
Here are some links I tried:
XMLHttpRequest cannot load URL. Origin not allowed by Access-Control-Allow-Origin
http://goo.gl/JuS5B1
You don't. The server you are making the request to has to implement CORS to grant JavaScript from your website access. Your JavaScript can't grant itself permission to access another website.
I had a similar problem and for me it boiled down to adding the following HTTP headers at the response of the receiving end:
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Origin: *
You may prefer not to use the * at the end, but only the domainname of the host sending the data. Like *.example.com
But this is only feasible when you have access to the configuration of the server.
Try using the resource service to consume flickr jsonp:
var MyApp = angular.module('MyApp', ['ng', 'ngResource']);
MyApp.factory('flickrPhotos', function ($resource) {
return $resource('http://api.flickr.com/services/feeds/photos_public.gne', { format: 'json', jsoncallback: 'JSON_CALLBACK' }, { 'load': { 'method': 'JSONP' } });
});
MyApp.directive('masonry', function ($parse) {
return {
restrict: 'AC',
link: function (scope, elem, attrs) {
elem.masonry({ itemSelector: '.masonry-item', columnWidth: $parse(attrs.masonry)(scope) });
}
};
});
MyApp.directive('masonryItem', function () {
return {
restrict: 'AC',
link: function (scope, elem, attrs) {
elem.imagesLoaded(function () {
elem.parents('.masonry').masonry('reload');
});
}
};
});
MyApp.controller('MasonryCtrl', function ($scope, flickrPhotos) {
$scope.photos = flickrPhotos.load({ tags: 'dogs' });
});
Template:
<div class="masonry: 240;" ng-controller="MasonryCtrl">
<div class="masonry-item" ng-repeat="item in photos.items">
<img ng-src="{{ item.media.m }}" />
</div>
</div>
This issue occurs because of web application security model policy that is Same Origin Policy Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. That means requester must match the exact host, protocol, and port of requesting site.
We have multiple options to over come this CORS header issue.
Using Proxy - In this solution we will run a proxy such that when request goes through the proxy it will appear like it is some same origin.
If you are using the nodeJS you can use cors-anywhere to do the proxy stuff. https://www.npmjs.com/package/cors-anywhere.
Example:-
var host = process.env.HOST || '0.0.0.0';
var port = process.env.PORT || 8080;
var cors_proxy = require('cors-anywhere');
cors_proxy.createServer({
originWhitelist: [], // Allow all origins
requireHeader: ['origin', 'x-requested-with'],
removeHeaders: ['cookie', 'cookie2']
}).listen(port, host, function() {
console.log('Running CORS Anywhere on ' + host + ':' + port);
});
JSONP - JSONP is a method for sending JSON data without worrying about cross-domain issues.It does not use the XMLHttpRequest object.It uses the <script> tag instead. https://www.w3schools.com/js/js_json_jsonp.asp
Server Side - On server side we need to enable cross-origin requests.
First we will get the Preflighted requests (OPTIONS) and we need to allow the request that is status code 200 (ok).
Preflighted requests first send an HTTP OPTIONS request header to the resource on the other domain, in order to determine whether the actual request is safe to send. Cross-site requests are preflighted like this since they may have implications to user data. In particular, a request is preflighted if it uses methods other than GET or POST. Also, if POST is used to send request data with a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain, e.g. if the POST request sends an XML payload to the server using application/xml or text/xml, then the request is preflighted.
It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)
If you are using the spring just adding the bellow code will resolves the issue.
Here I have disabled the csrf token that doesn't matter enable/disable according to your requirement.
#SpringBootApplication
public class SupplierServicesApplication {
public static void main(String[] args) {
SpringApplication.run(SupplierServicesApplication.class, args);
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
};
}
}
If you are using the spring security use below code along with above code.
#Configuration
#EnableWebSecurity
public class SupplierSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll().antMatchers("/**").authenticated().and()
.httpBasic();
}
}
I encountered a similar problem like this, problem was with the backend . I was using node server(Express). I had a get request from the frontend(angular) as shown below
onGetUser(){
return this.http.get("http://localhost:3000/user").pipe(map(
(response:Response)=>{
const user =response.json();
return user;
}
))
}
But it gave the following error
This is the backend code written using express without the headers
app.get('/user',async(req,res)=>{
const user=await getuser();
res.send(user);
})
After adding a header to the method problem was solved
app.get('/user',async(req,res)=>{
res.header("Access-Control-Allow-Origin", "*");
const user=await getuser();
res.send(user);
})
You can get more details about Enabling CORS on Node JS
This answer outlines two ways to workaround APIs that don't support CORS:
Use a CORS Proxy
Use JSONP if the API Supports it
One workaround is to use a CORS PROXY:
angular.module("app",[])
.run(function($rootScope,$http) {
var proxy = "//cors-anywhere.herokuapp.com";
var url = "http://api.ipify.org/?format=json";
$http.get(proxy +'/'+ url)
.then(function(response) {
$rootScope.response = response.data;
}).catch(function(response) {
$rootScope.response = 'ERROR: ' + response.status;
})
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
Response = {{response}}
</body>
For more information, see
GitHub: CORS Anywhere
Use JSONP if the API supports it:
var url = "//api.ipify.org/";
var trust = $sce.trustAsResourceUrl(url);
$http.jsonp(trust,{params: {format:'jsonp'}})
.then(function(response) {
console.log(response);
$scope.response = response.data;
}).catch(function(response) {
console.log(response);
$scope.response = 'ERROR: ' + response.status;
})
The DEMO on PLNKR
For more information, see
AngularJS $http Service API Reference - $http.jsonp
Answered by myself.
CORS angular js + restEasy on POST
Well finally I came to this workaround:
The reason it worked with IE is because IE sends directly a POST instead of first a preflight request to ask for permission.
But I still don't know why the filter wasn't able to manage an OPTIONS request and sends by default headers that aren't described in the filter (seems like an override for that only case ... maybe a restEasy thing ...)
So I created an OPTIONS path in my rest service that rewrites the reponse and includes the headers in the response using response header
I'm still looking for the clean way to do it if anybody faced this before.
Apache/HTTPD tends to be around in most enterprises or if you're using Centos/etc at home. So, if you have that around, you can do a proxy very easily to add the necessary CORS headers.
I have a blog post on this here as I suffered with it quite a few times recently. But the important bit is just adding this to your /etc/httpd/conf/httpd.conf file and ensuring you are already doing "Listen 80":
<VirtualHost *:80>
<LocationMatch "/SomePath">
ProxyPass http://target-ip:8080/SomePath
Header add "Access-Control-Allow-Origin" "*"
</LocationMatch>
</VirtualHost>
This ensures that all requests to URLs under your-server-ip:80/SomePath route to http://target-ip:8080/SomePath (the API without CORS support) and that they return with the correct Access-Control-Allow-Origin header to allow them to work with your web-app.
Of course you can change the ports and target the whole server rather than SomePath if you like.
var result=[];
var app = angular.module('app', []);
app.controller('myCtrl', function ($scope, $http) {
var url="";// your request url
var request={};// your request parameters
var headers = {
// 'Authorization': 'Basic ' + btoa(username + ":" + password),
'Access-Control-Allow-Origin': true,
'Content-Type': 'application/json; charset=utf-8',
"X-Requested-With": "XMLHttpRequest"
}
$http.post(url, request, {
headers
})
.then(function Success(response) {
result.push(response.data);
$scope.Data = result;
},
function Error(response) {
result.push(response.data);
$scope.Data = result;
console.log(response.statusText + " " + response.status)
});
});
And also add following code in your WebApiConfig file
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
we can enable CORS in the frontend by using the ngResourse module.
But most importantly, we should have this piece of code while making the ajax
request in the controller,
$scope.weatherAPI = $resource(YOUR API,
{callback: "JSON_CALLBACK"}, {get: {method: 'JSONP'}});
$scope.weatherResult = $scope.weatherAPI.get(YOUR REQUEST DATA, if any);
Also, you must add ngResourse CDN in the script part and add as a dependency
in the app module.
<script src="https://code.angularjs.org/1.2.16/angular-resource.js"></script>
Then use "ngResourse" in the app module dependency section
var routerApp = angular.module("routerApp", ["ui.router", 'ngResource']);
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()
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.
I successfully managed to use interceptors (AngularJs ) in my Ionic application. Previous post .
While it is working perfectly well in the browser using "ionic serve".
There is no content loaded in the header title and content block ( "ion-content" ) using "ionic run android" (either emulating on genymotion or on my own phone). See screenshot below.
I'm pretty sure it comes from the interceptors i'm using, because before that, the app was working on any platforms. Also, as soon as i remove the interceptors it is working again. Here the code.
Note that i'm checking which url is called so i don't go into a circular dependency or checking useless url, only the calls to my api go through.
app.config(function($httpProvider){
$httpProvider.interceptors.push(['$location', '$injector', '$q', function($location, $injector, $q){
return {
'request' : function(config){
// intercept request
// carefull includes might not work while emulating
// use instead indexOf for that case
if(!config.url.includes('/oauth/v2/token') && config.url.includes('/api')){
// inject the service manually
var OauthService = $injector.get('OauthService');
var access_token = OauthService.token();
config.url = config.url+'?access_token='+access_token.key;
}
return config;
}
}
}]);
});
Any ideas what could give this error? (By the way console is showing no errors on browser).
UPDATE :
OauthService.js :
app.factory('OauthService', function($http, $localStorage) {
return {
token : function(){
// Store actual token
access_token = $localStorage.getObject('access_token');
// Store actual identity
identity_token = $localStorage.getObject('identity_token');
// IF no user logged
if(isObjectEmpty(identity_token)){
// IF access_token does NOT exist OR will expires soon
if( isObjectEmpty(access_token) || Date.now() > (access_token.expires_at - (600*1000)) ){
// Create an anonymous access_token
return $http
.get(domain+'/oauth/v2/token?client_id='+public_id+'&client_secret='+secret+'&grant_type=client_credentials')
.then(function (response) {
$localStorage.setObject('access_token', {
key: response.data.access_token,
type: 'anonymous',
expires_at: Date.now()+(response.data.expires_in*1000)
});
return response.data.access_token;
});
}
}
// IF user is logged
else{
// IF access_token does NOT exist OR will expires soon OR is anonymous
if( isObjectEmpty(access_token) || Date.now() > (access_token.expires_at - (600*1000)) || access_token.type == 'anonymous' ){
// Create an access_token with an identity
return $http
.get(domain+'/oauth/v2/token?client_id='+public_id+'&client_secret='+secret+'&api_key='+identity_token+'&grant_type=http://oauth2.dev/grants/api_key')
.then(function (response) {
$localStorage.setObject('access_token', {
key: response.data.access_token,
type: 'identity',
expires_at: Date.now()+(response.data.expires_in*1000)
});
return response.data.access_token;
});
}
}
return access_token.key;
}
};
})
Did you install cordova whitelist plugin ?
cordova plugin add cordova-plugin-whitelist
or if you want to save the reference to your config.xml file:
cordova plugin add cordova-plugin-whitelist --save
If you don't have that your device won't be able to access external resources.
You can find more info here.
UPDATE:
I've checked your previous answer.
The idea of the interceptor is to intercept calls to an external service insert some action in the pipeline.
I would change your interceptor:
$httpProvider.interceptors.push(['$location', '$injector', '$q', '$localStorage', function($location, $injector, $q, $localStorage){
return {
'request' : function(config) {
config.headers = config.headers || {};
access_token = $localStorage.getObject('access_token');
if (access_token) {
config.headers.Authorization = 'Bearer ' + access_token;
}
}
'response' : function(response){
if (response.status === 401) {
logger.debug("Response 401");
}
return response || $q.when(response);
}
'responseError' : function(rejection){
if (rejection.status === 401) {
var OauthService = $injector.get('OauthService');
var access_token = OauthService.token();
if (access_token === null)
{
return $q.reject(rejection);
}
// Append your access token to the previous request and re-submits.
rejection.config.headers['Authorization'] = 'Bearer ' + access_token;
return $injector.get('$http')(rejection.config);
}
// This is necessary to make a `responseError` interceptor a no-op.
return $q.reject(rejection);
}
}
}]);
If you look at the interceptor above it manages all the requests to an external resource (REST api) and appends a bearer token to the authorization header if needed.
The response does not do much as it is only there for logging purposes.
responseError is the place where you should intercept and check if your token as expired, fetch a new one and resubmit the request.
We check if the user is not authorized to the request:
if (rejection.status === 401) { ... }
If not we request a new access token. I guess your OauthService does that.
If we have a new access token:
var access_token = OauthService.token();
we can, again, append the access token to the request header:
rejection.config.headers['Authorization'] = 'Bearer ' + access_token;
and resubmit the previous request:
return $injector.get('$http')(rejection.config);
If you want to find out more about interceptors you can read these blogs.
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");
});