I'm creating a web app using AngularJS. To test it, I'm running the app in a NodeJS server, using angular-seed template.
In this app, I need to send a JSON message to another host, via POST request, and get the response, so, I'm using CORS.
My request is done by implementing a service that uses AngularJS http service (I need the level of abstraction that $http provides. So, I don't use $resource).
Here, my code. Please pay attention to the fact that I modify $httpProvider to tell AngularJS to send its requests with the appropriate CORS headers.
angular.module('myapp.services', []).
// Enable AngularJS to send its requests with the appropriate CORS headers
// globally for the whole app:
config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
/**
* Just setting useXDomain to true is not enough. AJAX request are also
* send with the X-Requested-With header, which indicate them as being
* AJAX. Removing the header is necessary, so the server is not
* rejecting the incoming request.
**/
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
]).
factory('myService', function($http) {
return {
getResponse: function() {
var exampleCommand = JSON.stringify({"foo": "bar"});
// This really doesn't make a difference
/*
var config = {headers: {
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Headers': 'Content-Type, Content-Length, Accept',
'Content-Type': 'application/json'
}
};
*/
//return $http.post(REMOTE_HOST, exampleCommand, config).
return $http.post(REMOTE_HOST, exampleCommand).
success(function(data, status, headers, config) {
console.log(data);
return data;
}).
error(function (data, status, headers, config) {
return {'error': status};
});
}
}
});
The problem is I can't make it work. I always get this error message:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at REMOTE_HOST. This can be fixed by moving the
resource to the same domain or enabling CORS.
But if I do a simple jQuery AJAX call like this:
$.ajax(REMOTE_HOST,
{
dataType: "json",
type: "POST",
data: exampleCommand,
success: function(data) { console.log(data); },
error: function(request, textStatus, errorThrown) { console.log("error " + textStatus + ": " + errorThrown);}
});
It works fine.
So, my questions:
- How do I allow cross-site requests in an AngularJS running under NodeJS?
UPDATE: Thanks to Dayan Moreno Leon's response.
My problem is I need to add cors support to my server. I'm using NodeJS http-server for development and lighttpd for production.
- Why does the simple jQuery POST request work but AngularJS POST request doesn't?
I guess jQuery AJAX requests are cross-domain by default. Not really sure yet.
Many thanks in advance
CORS is not handled on the client but in the server you need to allow CORS on your nodejs app where your angular app is trying to POST. you can try using cors module if you are using express
https://www.npmjs.org/package/cors
other whise you need to check for the options method and return 200 as a response
http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
Why does the simple jQuery POST request work but AngularJS POST request doesn't?
jQuery uses simple requests while AngularJS uses preflighted requests
In your angular code you can add set Content-Type to application/x-www-form-urlencoded and encode your data using $.param
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 working on an internal web application at work. In IE10 the requests work fine, but in Chrome all the AJAX requests (which there are many) are sent using OPTIONS instead of whatever defined method I give it. Technically my requests are "cross domain." The site is served on localhost:6120 and the service I'm making AJAX requests to is on 57124. This closed jquery bug defines the issue, but not a real fix.
What can I do to use the proper http method in ajax requests?
Edit:
This is in the document load of every page:
jQuery.support.cors = true;
And every AJAX is built similarly:
var url = 'http://localhost:57124/My/Rest/Call';
$.ajax({
url: url,
dataType: "json",
data: json,
async: true,
cache: false,
timeout: 30000,
headers: { "x-li-format": "json", "X-UserName": userName },
success: function (data) {
// my success stuff
},
error: function (request, status, error) {
// my error stuff
},
type: "POST"
});
Chrome is preflighting the request to look for CORS headers. If the request is acceptable, it will then send the real request. If you're doing this cross-domain, you will simply have to deal with it or else find a way to make the request non-cross-domain. This is why the jQuery bug was closed as won't-fix. This is by design.
Unlike simple requests (discussed above), "preflighted" requests first
send an HTTP request by the OPTIONS method 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, HEAD 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)
Based on the fact that the request isn't sent on the default port 80/443 this Ajax call is automatically considered a cross-origin resource (CORS) request, which in other words means that the request automatically issues an OPTIONS request which checks for CORS headers on the server's/servlet's side.
This happens even if you set
crossOrigin: false;
or even if you ommit it.
The reason is simply that localhost != localhost:57124. Try sending it only to localhost without the port - it will fail, because the requested target won't be reachable, however notice that if the domain names are equal the request is sent without the OPTIONS request before POST.
I agree with Kevin B, the bug report says it all. It sounds like you are trying to make cross-domain ajax calls. If you're not familiar with the same origin policy you can start here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Same_origin_policy_for_JavaScript.
If this is not intended to be a cross-domain ajax call, try making your target url relative and see if the problem goes away. If you're really desperate look into the JSONP, but beware, mayhem lurks. There really isn't much more we can do to help you.
If it is possible pass the params through regular GET/POST with a different name and let your server side code handles it.
I had a similar issue with my own proxy to bypass CORS and I got the same error of POST->OPTION in Chrome. It was the Authorization header in my case ("x-li-format" and "X-UserName" here in your case.) I ended up passing it in a dummy format (e.g. AuthorizatinJack in GET) and I changed the code for my proxy to turn that into a header when making the call to the destination. Here it is in PHP:
if (isset($_GET['AuthorizationJack'])) {
$request_headers[] = "Authorization: Basic ".$_GET['AuthorizationJack'];
}
In my case I'm calling an API hosted by AWS (API Gateway). The error happened when I tried to call the API from a domain other than the API own domain. Since I'm the API owner I enabled CORS for the test environment, as described in the Amazon Documentation.
In production this error will not happen, since the request and the api will be in the same domain.
I hope it helps!
As answered by #Dark Falcon, I simply dealt with it.
In my case, I am using node.js server, and creating a session if it does not exist. Since the OPTIONS method does not have the session details in it, it ended up creating a new session for every POST method request.
So in my app routine to create-session-if-not-exist, I just added a check to see if method is OPTIONS, and if so, just skip session creating part:
app.use(function(req, res, next) {
if (req.method !== "OPTIONS") {
if (req.session && req.session.id) {
// Session exists
next();
}else{
// Create session
next();
}
} else {
// If request method is OPTIONS, just skip this part and move to the next method.
next();
}
}
"preflighted" requests first send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send. Cross-site requests
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
Consider using axios
axios.get( url,
{ headers: {"Content-Type": "application/json"} } ).then( res => {
if(res.data.error) {
} else {
doAnything( res.data )
}
}).catch(function (error) {
doAnythingError(error)
});
I had this issue using fetch and axios worked perfectly.
I've encountered a very similar issue. I spent almost half a day to understand why everything works correctly in Firefox and fails in Chrome. In my case it was because of duplicated (or maybe mistyped) fields in my request header.
Use fetch instead of XHR,then the request will not be prelighted even it's cross-domained.
$.ajax({
url: '###',
contentType: 'text/plain; charset=utf-8',
async: false,
xhrFields: {
withCredentials: true,
crossDomain: true,
Authorization: "Bearer ...."
},
method: 'POST',
data: JSON.stringify( request ),
success: function (data) {
console.log(data);
}
});
the contentType: 'text/plain; charset=utf-8', or just contentType: 'text/plain', works for me!
regards!!
I have an Ionic App that I am using restangular to communicate with a node express application.
Everything is working when I have the node express application configured to use http.
Ionic App side:
RestangularProvider.setBaseUrl('http://11.22.33.44:3000');
// custom header
interceptors.serialNumber = function (element, operation, what, url, headers, query) {
return {
headers: angular.extend({
'x-serialnumber': deviceStore.serialNumber
}, headers)
};
};
Restangular.one(‘Admin’).get()
.then(function (data) {
console.log(data);
}, function (error) {
console.log(error);
});
Node Express App side:
var app = express();
app.use(cors());
app.get('/Admin, function(req, res) {
console.log(admin-get');
res.send(200);
});
I was expecting I would need to handle a pre-flight request since the cors node module states: “An example of a 'complex' CORS request is one that uses an HTTP verb other than GET/HEAD/POST (such as DELETE) or that uses custom headers.” So I am not sure why this works?
I reconfigure the Ionic App and Node Express App to use a https address instead of a http:
Ionic App side:
RestangularProvider.setBaseUrl('https://11.22.33.44:3000');
// custom header
interceptors.serialNumber = function (element, operation, what, url, headers, query) {
return {
headers: angular.extend({
'x-serialnumber': deviceStore.serialNumber
}, headers)
};
};
Restangular.one(‘Admin’).get()
.then(function (data) {
console.log(data);
}, function (error) {
console.log(error);
});
Node Express App side:
var app = express();
app.use(cors());
app.get('/Admin, function(req, res) {
console.log(admin-get');
res.send(200);
});
when the Ionic App performs the GET request, I see in the Chrome debugger under “Network” an OPTIONS request that gets canceled (request’s status). This tells me that I need to enable cors pre-flight on my Node Express App side (though why didn’t I see this error when the server was configured with http instead of https?).
So I tried the following on the Node Express App side per the express js cors module documentation:
app.options('Admin', cors()); // enable pre-flight request
app.get('/Admin', cors(), function(req, res) {
console.log('admin-get');
res.send(200);
});
I see the same thing in the Chrome debugger under “Network” - a OPTIONS request that gets canceled (request’s status). I also tried
app.options('*', cors());
with the same result.
I then removed the insertion of the custom header (x-serialnumber) on the Ionic App side. It now works.
So why would the Node Express Application work when configured with a http address with out handling a pre-flight request I would expect due to the insertion of a custom header on the Ionic App side?
When the Node Express App is configured with a https address (as well as Ionic App side) why am I not handling the OPTIONS request? Is the way I am configuring cors incorrect? What am I missing?
I think it is a cors issue since I can eliminate the custom header on the Ionic App side when they are configured for a https address and it works.
What do I need to do to get this working?
UPDATE
I tried using Angular JS $http instead of Restangular. I got the following result which works:
$http({
method: 'GET',
url: theUrl
}).then(function successCallback(response) {
$http({
method: 'GET',
url: theUrl,
headers: {
'x-serialnumber' : deviceStore.serialNumber
}
}).then(function successCallback(response) {
}, function errorCallback(response) {
});
}, function errorCallback(response) {
});
I see in Chrome Network Debugger the first GET (minus the custom header) goes out and I get a good response (200), followed by the OPTIONS request where I also get a good response (200), followed by a good GET with the custom header in it (get good response back).
If I do NOT do this first GET request minus the custom header, the OPTIONS request aborts on the Angular JS Ionic App side with a status of -1.
NOTE: I can change out the initial $http request (minus custom header) for a Restangular request (minus custom header)
Why is this initial GET minus the custom header needed (i.e. GET (minus custom header) | OPTIONS | GET (with custom header))?
What do I not understand?
Well what I needed to do was the following:
$http({
method: 'GET',
url: 'https://example.com/DUMMY'
}).then(function successCallback(response) {
$http({
method: 'GET',
url: 'https://example.com',
headers: {
'x-serialnumber': deviceStore.serialNumber
}
}).then(function successCallback(response) {
console.log('SUCCESS');
}, function errorCallback(response) {
console.log('FAILURE');
});
}, function errorCallback(response) {
console.log('FAILURE');
});
In essence, I needed to send a "preliminary" GET request with NO custom header. The GET request could be to anything on my node express server. After this "preliminary" GET request, I could perform the GET request with the custom header in it.
Specifically on the server side I see the following:
GET /DUMMY 200 10ms - 2b
OPTIONS / 204 1ms
GET / 200 13ms - 1.03kb
Without performing this "preliminary" get request, the OPTIONS request in my Ionic App would abort - status code = -1 - usually means the request was aborted and would never leave Ionic App side.
I still do not understand why I need this "preliminary" GET request, but this works for me.
I'm using AngularJS along with Node.js and I'm trying to read title from a specific url
I have something like this:
App.config(function($httpProvider) {
//Enable cross domain calls
$httpProvider.defaults.useXDomain = true;
//Remove the header used to identify ajax call that would prevent CORS from working
delete $httpProvider.defaults.headers.common['X-Requested-With'];
});
And I try to read urls title with:
$http({ url:$scope.url,method:"POST", headers: {'Content-Type': 'application/x-www-form- urlencoded'}}).success(function(data){
console.log(data);
});
But I get NetworkError: 405 Method Not Allowed and/or Cross-Origin Request Blocked:....
It doesn't matter what your client-side code is, you'll need to enable CORS on the server side to allow Cross-Origin requests.
I am trying to use the $http service on a HTTPS URL with the following code :
var request = $http.post('https://my.custom.url/webservice', privateAttributes.requestData);
request.success(function(data, status) {
}).error(function(data, status) {
console.log('data -', data);
console.log('status -', status);
});
The my.custom.url is on a different domain as my angularJS app, but my webserver is well configured to allow cross domain XHR request. It's supposed to be a public webservice.
When the request is sent, the promise is instantly rejected, so the error() function is triggered. The data is undefined and the status is 0.
On the network tab of my debugger in Chrome, I can see a pending OPTIONS request corresponding to my $http.post() call.
For testing purpose, I tried to do the same request with jQuery $.post() method and it worked with no issue. I think I am making something wrong with the $http service.
Please note that it's not a XSRF issue and if I use the HTTP version of my webservice, the request is a success.
Thanks for your help.
You might need to tell it to send the cookie:
In your config, DI $httpProvider and then set withCredentials to true:
.config(function ($routeProvider, $httpProvider) {
$httpProvider.defaults.withCredentials = true;
//rest of route code
Info on angularjs withCredentials: http://docs.angularjs.org/api/ng.$http
Which links to the mozilla article: https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control#section_5