AngularJS: POST Data to External REST API - javascript

I have a basic AngularJS service setup like so:
app.factory('User', function($resource) {
return $resource('http://api.mysite.com/user/:action:id/:attr', {}, {
history: {
method: 'GET',
params: {
attr: 'history'
}
},
update: {
method: 'POST',
params: {
name: 'test'
}
}
});
});
and I use it like this:
User.history({id: 'testID'}, function(data) {
console.log('got history');
console.log(data);
});
User.update({id: 'me'}, function(data) {
console.log('updated');
console.log(data);
});
Problem one: User.update(), despite having the method set to POST, keeps sending OPTIONS as the request method.
Though Chrome Dev tools reports the request header Access-Control-Request-Method:POST is sent as well (Not sure if that means anything).
Problem two: I keep getting an error with CORS, despite having these headers set in the API code:
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS");
This problem only shows up though if making a non-GET request.
What's the proper way to be handling this? I've also looked into JSONP, but with this being a RESTful api, I'm not sure how to get around the problems with only GET support.

Your two problems are actually one problem. The OPTIONS request is part of the CORS process. For POST requests, the browser first sends an OPTIONS call, and the server responds if it is okay to execute it.
If the OPTIONS request fails, Angular / Chrome shows you the reason in the console. For example:
OPTIONS https://*** Request header field Content-Type is not allowed by Access-Control-Allow-Headers. angular.min.js:106
XMLHttpRequest cannot load https://***. Request header field Content-Type is not allowed by Access-Control-Allow-Headers.
You probably have to set Access-Control-Allow Headers on the server, too:
header('Access-Control-Allow-Headers: Content-Type, x-xsrf-token')
x-xrsf-token is for angular' to prevent CSRF. You may have to add more headers, depending on what you send from the client.
Here is a very good guide on CORS: https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS

In AngularJS to make CORS working you also have to overwrite default settings of the angular httpProvider:
var myApp = angular.module('myApp', [
'myAppApiService']);
myApp.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
]);
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.
Note: Answer only works for older AngularJS version previous to 1.2. With 1.2 and above you don't have to do do anything to enable CORS.

Better to solve this problem at the server. On apache you can solve it like this in a .htaccess file. This is a source of pain for angular development and can be solved in angular as well but its probably not the best way to do it.
Header set Access-Control-Allow-Origin "*"
Header add Access-Control-Allow-Headers "origin, x-requested-with, content-type"
Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"

Related

Ionic app CORS No 'Access-Control-Allow-Origin' header is present, using Lumen API with CORS middleware

I am building an app which uses a Lumen API. On the Lumen project I have two files which I found on the internet whilst looking up how to handle CORS in Lumen.
CorsMiddleware.php:
<?php
namespace App\Http\Middleware;
class CorsMiddleware {
public function handle($request, \Closure $next)
{
$response = $next($request);
$response->header('Access-Control-Allow-Methods', 'HEAD, GET, POST, PUT, PATCH, DELETE');
$response->header('Access-Control-Allow-Headers', $request->header('Access-Control-Request-Headers'));
$response->header('Access-Control-Allow-Origin', '*');
return $response;
}
}
CatchAllOptionsRequestsProvider.php:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
/**
* If the incoming request is an OPTIONS request
* we will register a handler for the requested route
*/
class CatchAllOptionsRequestsProvider extends ServiceProvider {
public function register()
{
$request = app('request');
if ($request->isMethod('OPTIONS'))
{
app()->options($request->path(), function() { return response('', 200); });
}
}
}
These two files fixed my initial CORS issue. I am able to perform a GET and receive data from the API. But when I try a POST method to the API I once again get the following error: "No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8100' is therefore not allowed access."
Upon inspecting the network tab in chrome, There are two requests. The first is an OPTIONS request, which I believe is just to get the allowed headers from the server. The second request is my POST request with the correct payload. They both return a status code of 200 OK but I still get the Access-Control error mentioned above.
It works when using POSTMAN to send data to my API, but not when I use Ionic Serve in the browser
For those who are wondering, I am using Ionic's $http method for the call:
MORE CODE.......
var req = {
method: 'POST',
url: APIUrl + 'register',
timeout: timeout.promise,
data: {"name": "Michael"}
}
$http(req).then(function(res) {
.......MORE CODE
Might it be something to do with the server apache config? I have mod_rewrite enabled.
Any help on this would be greatly appreciated. Thanks
If you are in control of the server, you might need to set the required headers there. Depending on which server, this might help:
http://enable-cors.org/server.html

AJAX request with headers failing

I'm trying to do an AJAX request to https://developers.zomato.com/api/v2.1/search referring to Zomato API
The server has headers:
"access-control-allow-methods": "GET, POST, DELETE, PUT, PATCH, OPTIONS",
"access-control-allow-origin": "*"
The problem is that the API requires additional headers set for user-key. But whenever I set custom headers then chrome would do a pre-flight request by sending an OPTIONS request to the above URL which is failing, and thus the AJAX request is failing as well.
If I don't set the headers, then I don't get a CORS error, but rather a forbidden error from server since I'm not setting user-key header.
Any way to go about this catch-22 situation?
Both Jquery and JavaScript way are failing:
$(document).ready(function () {
$.ajax({
url: 'https://developers.zomato.com/api/v2.1/search',
headers: {
'Accept': 'application/json',
'user_key': 'XXXXX'
},
success: function (data) {
console.log(data);
}
});
});
var xhr = new XMLHttpRequest();
var url = 'https://developers.zomato.com/api/v2.1/search';
xhr.open('GET', url, false);
xhr.setRequestHeader('Accept', 'application/json');
xhr.setRequestHeader('user_key', 'XXXXXX');
xhr.send(null);
if (xhr.status == 200) {
console.log(xhr.responseText);
}
Error I'm getting:
OPTIONS https://developers.zomato.com/api/v2.1/search
XMLHttpRequest cannot load https://developers.zomato.com/api/v2.1/search. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access. The response had HTTP status code 501.
If somebody wants to reproduce you can get a free user-key here:
https://developers.zomato.com/api
There does not appear to be a work-around for this issue from a browser. The CORS specification requires a browser to preflight the request with the OPTIONS request if any custom headers are required. And, when it does the OPTIONS preflight, it does not include your custom headers because part of what the OPTIONS request is for is to find out what custom headers are allowed to be sent on the request. So, the server is not supposed to require custom headers on the OPTIONS request if it wants this to work from a browser.
So, if the server is requiring the custom headers to be on the OPTIONS request, then the server is just expecting something that will not happen from a browser.
See related answers that describe more about this here:
jQuery CORS Content-type OPTIONS
Cross Domain AJAX preflighting failing Origin check
How do you send a custom header in a cross-domain (CORS) XMLHttpRequest?
Using CORS for Cross-Domain Ajax Requests
And, another user with the same issue here:
Zomato api with angular
It appears the Zomato is not browser friendly, but requires access from a server where you don't have CORS restrictions.
FYI, the error coming back from Zomato is 501 which means NOT IMPLEMENTED for the OPTIONS command. So, it looks like it's not only that the key is not being sent with the OPTIONS command, but that Zomato does not support the OPTIONS command, but that is required for the use of custom headers on a cross-origin request from a browser.
You can't bypass Access-Control-Allow-Headers in preflight response.
However as mentioned by #Jaromanda X in comments, Zomato sends:
Access-Control-Allow-Headers:X-Zomato-API-Key
...meaning you can only send this non-standard header from browser. Also don't go too low-level in request definition when jQuery has pretty and prepared shorthands ...
TL;DR Working example:
$.ajax({
type: "GET", //it's a GET request API
headers: {
'X-Zomato-API-Key': 'YOUR_API_KEY' //only allowed non-standard header
},
url: 'https://developers.zomato.com/api/v2.1/dailymenu', //what do you want
dataType: 'json', //wanted response data type - let jQuery handle the rest...
data: {
//could be directly in URL, but this is more pretty, clear and easier to edit
res_id: 'YOUR_RESTAURANT_OR_PLACE_ID',
},
processData: true, //data is an object => tells jQuery to construct URL params from it
success: function(data) {
console.log(data); //what to do with response data on success
}
});

AngularJS & Laravel CORS, POST stops after preflights OPTIONS

This is the story of a bird who wants to work for the post but fails during his preflight test...
App built with Laravel being used as a RESTful API and AngularJS/ionic.
My API calls were working fine until...for an unknown reason it stopped.
Although I set the withCredentials for the angularJS side of the call, the preflight OPTIONS are not sending a cookie but I am receiving one back from Laravel. How can we disable OPTIONS to return a cookie laravel_session?
It messes up the CORS as it sets a new session which will obviously be different on every POST.
For Laravel side I use the package Laravel/CORS from #barryvdh with the following configuration:
'*' => array(
'supportsCredentials' => true,
'allowedOrigins' => array('*'),
'allowedHeaders' => array('*'),
'allowedMethods' => array('POST', 'PUT', 'GET', 'PATCH', 'OPTIONS', 'DELETE'),
'maxAge' => 36000,
'hosts' => array('api.*'),
)
On the Angular side I have the following:
$http({
method: 'POST',
url: 'http://api.blabla.local/banana',
data: data,
withCredentials: true
})
My GET calls work fine and I have one running at start of the app to fetch the CSRF from laravel that I send back when needed.
Right now the following happens:
1. Preflight OPTIONS > request has no cookies for the session. Reponse = 200 with a different session cookie which will cause the CSRF to cause all the time. [thoughts: the withCredentials does not work with the OPTIONS call]
2. POST > fails with 500, in the headers I see no response but it did send the cookie/session [thoughts: credentials are passed to it but they are also the wrong ones since they have changed on server side because of the preflight option]. Error message says it is not authorized origin.
What's going on? I've been trying for hours now and checked a lot of other posts but nothing seems to help! Can I get rid of the preflight, how? Or is the problem somewhere else (server side I'm using Laravel Homestead)?
I feel that the real issue is that the OPTIONS returns a session cookie or simply that the request does include one!
Thanks for your help, I've been stuck for hours and I'm going crazzy on that...
In the filters.php under L4.2 I ended up using this:
The problem is old so not sure it's the only thing I did but looks like it:
App::before(function($request)
{
//
// Enable CORS
// In production, replace * with http://yourdomain.com
header("Access-Control-Allow-Origin: http://mydomain.local");
header('Access-Control-Allow-Credentials: true'); //optional
if (Request::getMethod() == "OPTIONS") {
// The client-side application can set only headers allowed in Access-Control-Allow-Headers
$headers = [
'Access-Control-Allow-Methods'=> 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers'=> 'Content-Type'
];
return Response::make('You are connected to the API', 200, $headers);
}
});
App::after(function($request, $response)
{
//
});
JWT could be good for ionic and angular..
Check http://packalyst.com/packages/package/tymon/jwt-auth
also https://www.youtube.com/watch?v=vIGZxeQUUFU

HTTP GET request changes into OPTIONS while adding headers in angular js

Following is the request i used so far
$http.get(url)
.success(function (data){})
.error(function (data){})
works without any CORS issues. My server side Allows all origins, methods, all headers
when i add http header like
$http.get(url, { headers: { "USERID": user, "SESSIONID": sessionId}})
the request changes into OPTIONS method when i see in chrome dev tools network tab
What is the reason for this? if it is expected then how to add custom http headers.
I have gone thru this link angularjs-performs-an-options-http-request-for-a-cross-origin-resource but it didnt help
Here i am expecting that server should allow different origins . But it is allowing headers, only if i were in a same server. But not sure about this is by angular or by server side.
after headers
$http.get(url,{ headers: { "USERID": user, "SESSIONID": sessionId } })
in chrome dev tools i am seeing like
Request Method:OPTIONS
Status Code:404 Not Found
but without headers
Request Method:GET
Status Code:200 OK
When i do this in REST Client, i can send headers to the backend.
$http({method: 'GET', url: '/someUrl', headers: { "USERID": user, "SESSIONID": sessionId}}).
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
will work.
$http.get is a shortcut method.
Check the config in the docs
This is a known bug, see for instance https://github.com/angular/angular.js/issues/1585 .
A workaround is to use a jQuery request.
I had the same massive issue when trying to pass header in my get, where it changes get to options and wouldn't work. In order to make it work I added the following in my php api
<?php if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'GET') {
header("Access-Control-Allow-Headers: Authorization, X-Auth-Token");
}
exit;
} ?>
You can allow for any headers that you wish to pass.
Hope this helps
For my particular problem with my C# Web API solution I had to have something handle the Options request. Angular was sending a preflight request method OPTIONS which I did allow in my web.config with
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS, PATCH" />
But that wasn't enough I also included a method to handle the Options Request and I returned nothing
[ResponseType( typeof( void ) )]
public IHttpActionResult OptionsPost() {
return StatusCode( HttpStatusCode.NoContent );
}

AngularJS withCredentials Not Sending

In AngularJS, I have my Restful API in a subdomain but I am having the problem where the cookie/session is not being shared across domains. For Angular I am doing this:
app.config(['$httpProvider',
function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
$httpProvider.defaults.withCredentials = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}]);
Also when I am making a request with $http I am doing
var object = {};
object.url = '/example'
object.withCredentials = true;
$http(object).success(object.success).error(object.error);
And On my server side I have:
if($_SERVER['REQUEST_METHOD']=='OPTIONS') {
if(isset($_SERVER['HTTP_X_FOWARDED_HOST']) && !empty($_SERVER['HTTP_X_FOWARDED_HOST'])) {
$origin=$_SERVER['HTTP_X_FOWARDED_HOST'];
} else {
$origin=$_SERVER['HTTP_ORIGIN'];
}
if(isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && ($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']=='POST' || $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']=='DELETE' || $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']=='PUT')) {
header('Access-Control-Allow-Origin: '.$origin);
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Headers: *,X-Requested-With,Content-Type');
//header('Access-Control-Allow-Headers: Content-Type');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT');
// http://stackoverflow.com/a/7605119/578667
header('Access-Control-Max-Age: 86400');
}
}
Now I see that the server is saying that it will allow credentials but its not being sent in the options request. Screenshot below.
What am I doing wrong?
By default credentials are NOT sent in a CORS pre-flight OPTIONS request. See here. See also this answer. The credentials will be sent on your actual request.
Also, useXDomain and X-Request-With headers are not actually used in current versions of angular, so those lines are doing nothing in your $httpProvider config. All CORS interaction is handled by the browser itself and your server.
In general to properly implement CORS your server should not require credentials on the preflight request. (Please note that some browsers send them anyway, but shouldn't.) This is because an OPTIONS request is considered "safe" and should never contain any confidential information.
It may be your problem is in the cookies you're trying to share across domains. What cookies are you trying to send where?

Categories