I crafted an API with Symfony 4 that uses a custom token verification. I tested the API on Postman and everything works perfectly, now I want to use the API using jQuery and fetch all the data , but in the browser, I'm facing CORS issues like below:
Access to XMLHttpRequest at 'http://localhost:8000/api/reports' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Here is my server-side API:
I've implemented a CORSEventSubscriber to allow the CORS like below :
class CORSSubscriber implements EventSubscriberInterface
{
/**
* #var TokenStorageInterface
*/
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function onKernelResponse(FilterResponseEvent $event)
{
$responseHeaders = $event->getResponse()->headers;
$responseHeaders->set('Access-Control-Allow-Origin', '*');
$responseHeaders->set('Access-Control-Allow-Headers', 'x-auth-token, content-type');
$responseHeaders->set('Access-Control-Allow-Methods', 'POST, GET');
}
/**
* #inheritDoc
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => 'onKernelResponse',
];
}
This is the action that I'm calling in the controller:
/**
* #Route("/api/reports",name="reports",methods={"GET","OPTIONS"})
* #param Request $request
* #return Response
* #throws Exception
*/
function getReports(Request $request){
return new JsonResponse('test', Response::HTTP_UNAUTHORIZED);
}
I tried consuming the API like this
<script>
$(document).ready(function(){
authenticate().then(function (data) {
// the promise resolve the token and data.token output the correct value
$.ajax({
url:'http://localhost:8000/api/reports',
type: 'GET',
headers: {'X-Auth-Token' : data.token },
success: function (data) {
//append data to your app
console.log(data);
}
})
})
});
function authenticate() {
let data={
"username": "test",
"password": "test"
};
return new Promise(function(resolve, reject) {
$.ajax({
url:'http://localhost:8000/api/auth/check',
type:'POST',
data:JSON.stringify(data),
dataType:'Json',
success: function (data) {
console.log(data);
resolve(data);
},
error:function () {
}
})
});
}
</script>
I added this to debug closely the issue and i found out that this function only executes for POST when there's a token method like OPTIONS it doesn't execute
public function onKernelRequest(GetResponseEvent $event)
{
$this->logger->info($event->getRequest()->getRealMethod());
}
You are making a cross-origin request and adding a non-standard header. This means it is a Preflighted Request.
The browser is sending an OPTIONS request to ask permission to make the request with custom headers.
You can't control the format of the preflight request. You definitely can't add credentials to it. (Adding credentials is another thing which turns a simple request into a preflighted request).
You need to response to the OPTIONS request with permission via CORS headers. Since the request won't have any credentials associated with it your server must not require credentials.
Change the server to remove the requirement for the credentials when the request type of OPTIONS.
I don't know my way around the server-side framework you are using, but extrapolating from the code you've provided I suspect you should provide separate routes for GET and OPTIONS.
The OPTIONS request should concern itself only with CORS (and not fetch any data which requires authorisation).
The GET request should require authorisation and return the data.
After days through this, I fixed it by adding to the CorsSubscriber
public function onKernelResponse(FilterResponseEvent $event)
{
$responseHeaders = $event->getResponse()->headers;
$responseHeaders->set('Access-Control-Allow-Origin', 'http://localhost:8080');
$responseHeaders->set('Access-Control-Allow-Credentials', 'true');
$responseHeaders->set('Access-Control-Allow-Headers', ' content-type ,x-auth-token');
$responseHeaders->set('Access-Control-Allow-Methods', 'POST, GET');
if($event->getRequest()->getRealMethod()=='OPTIONS'){
$responseHeaders->set('Access-Control-Max-Age', '1728000');
$event->getResponse()->setStatusCode(200);
}
}
after handling the response I send 200 as status code so I won't have any CORS issue
Related
I want to send an api key for every request I make:
function MyService($http) {
var req = {
method: 'GET',
url: 'https://api.giphy.com/v1/stickers/trending',
headers: {
'api_key':'123'
}
}
return $http(req);
}
but the problem is that all requests are OPTIONS (not GET) and is not sending the api_key. Is that the right way to send headers? thanks
Editing because it was marked as duplicate:
This is not a CORS issue. The error I´m getting is 401. That means authentication failed because the endpoint is not receiving the request header with the api_key.
What you did is totally fine, but if the api_key is always different, so you have to provide the api_key value dynamically in order to be added to the request.
If it is always the same, you have a really better way to do that: through interceptors. And you will set that only one time. Again, this method is if you have to set up some parameter which is always the same, so actually it is for doing standard operations over HTTP requests.
First, you need to define your Interceptor:
myApp.service('MyRequestsInterceptor', [function() {
this.request = function(config) {
config.headers.api_key = 'My Default API KEY';
return config;
};
}]);
And then simply add your interceptor to AngularJS $httpProvided:
myApp.config([ '$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('MyRequestsInterceptor');
} ]);
I am running an HTTP server with web::http::experimental::listener::http_listener from Microsoft C++ REST SDK 1.3.1 and try to write HTML&Javascript as a client to interact with the server.
almost without surprise I got ...
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ...... (Reason: CORS header 'Access-Control-Allow-Origin' missing).
How can I put Access-Control-Allow-Origin:* on the http listener side (in c++ code)??
Is it possible in C++ REST 1.3.1??
is there workaround except JSONP?
Server
#include <cpprest/http_listener.h>
#include <cpprest/json.h>
using namespace web;
using namespace web::http;
using namespace web::http::experimental::listener;
http_listener httpSrv;
httpSrv->support(methods::GET, handle_get);
void handle_get(http_request request)
{
const json::value response;
request.reply(status_codes::OK, response);
}
Client
Client with jQuery v1.12.4 (bounded to jQuery UI v1.12.0)
$("button").click(function () {
$.get(rest_url, function(data, status){
console.log(status);
console.log(data);
});
});
----------------- UPDATE -----------------------
Solution from the answer
SERVER
http_listener httpSrv;
httpSrv.support(methods::GET, handle_get);
httpSrv.support(methods::POST, handle_post);
httpSrv.support(methods::OPTIONS, handle_options);
httpSrv.open().wait();
//...........
void handle_options(http_request request)
{
http_response response(status_codes::OK);
response.headers().add(U("Allow"), U("GET, POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
request.reply(response);
}
void handle_get(http_request request)
{
request.reply(status_codes::OK, ...);
}
void handle_post(http_request request)
{
json::value jsonResponse;
request
.extract_json()
.then([&jsonResponse](pplx::task<json::value> task)
{
jsonResponse = process_request(task.get());
})
.wait();
http_response response(status_codes::OK);
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.set_body(jsonResponse);
request.reply(response);
}
CLIENT
function requestREST(request/*json*/,onSuccess/*callback with json response*/) {
$.ajax({
type: "POST",
url: "...",
data: JSON.stringify(request),
dataType: 'json',
crossDomain: true,
contentType: "application/json",
success: function (response) {
onSuccess(response);
},
timeout:3000,
statusCode: {
400: function (response) {
alert('Not working!');
},
0: function (response) {
alert('Not working!');
}
}
});
To add headers on the server-side (C++), you'll need to modify the code you are using to send the response back.
At the moment, you're using:
request.reply(status_codes::OK, response);
Instead of doing that in a one-liner, the idea is to compose the response yourself starting from an empty response, add the desired header, set the actual body and then, send the response back to the client.
To constructs an empty response, we can use the following function:
web::http::http_response::http_response(http::status_code code)
As described in the documentation, it will constructs a response with a given status code, no headers and no body.
To access the headers of a response, we can use the following function:
web::http::http_response::headers()
The returned object will be of the http_headers type which contains a add function:
web::http::http_headers::add(const key_type &name, const _t1 &value)
This function will add a header to the response if it's provided a name and a value for the header.
When the header is set, the only thing remaining to set is the body. To do that, a response have the set_body function:
web::http::http_response::set_body(const json::value &body_data)
At the end, the complete code replacing your one-liner to create an empty response, set the header and the body and then send it back would look like:
http_response response(status_codes::OK);
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.set_body(jsonResponse);
request.reply(response);
Please note that in the last portion of the code, I'm using the U macro in order to create a string literal of the targeted platform type. You can find more informations regarding this U macro in the C++ Rest SDK FAQ.
Regarding preflight requests using the OPTION HTTP verb, these are expected in a situation like this. By default, the C++ REST SDK includes a default implementation for these requests. The default implementation can be checked in the source code:
void details::http_listener_impl::handle_options(http_request message)
{
http_response response(status_codes::OK);
response.headers().add(U("Allow"), get_supported_methods());
message.reply(response);
}
It is basically returning a 200 status code and adding a list of the supported methods your server can handle.
If you want to override the default implementation, for example to add some specific headers used by preflight requests like Access-Control-Allow-Methods or Access-Control-Allow-Headers, you will need to add a specific handler like you did for the GET and POST requests using:
web::http::experimental::listener::http_listener::support(const http::method &method, const std::function< void(http_request)> &handler)
It is not possible to use a general handler to handle OPTION request with:
web::http::experimental::listener::http_listener::support(const std::function<void(http_request)> &handler)
The reason why we can't use a general handler, if we take a look at the source code, is that if a method does not have a specific handler and is using the OPTION HTTP verb (or TRACE), the default handler implemented by the C++ REST SDK will be called:
// Specific method handler takes priority over general.
const method &mtd = msg.method();
if(m_supported_methods.count(mtd))
{
m_supported_methods[mtd](msg);
}
else if(mtd == methods::OPTIONS)
{
handle_options(msg);
}
I'am learning AngularJs and I've tried to write a very basic script sending an http request to Ebay public API, I've signed up and got my API keys, I've read the docs several times and wrote this basic code :
$scope.getQueryUrl = function () {
// Some unrelated code ...
$scope.queryUrl["Ebay"] = "http://svcs.sandbox.ebay.com/services/search/FindingService/v1?OPERATION-NAME=findItemsByKeywords&SERVICE-NAME=FindingService&SERVICE-VERSION=1.0.0&GLOBAL-ID=EBAY-US&SECURITY-APPNAME="+dataAuth.EbayKeyApi+"&RESPONSE-DATA-FORMAT=XML&keywords="+$scope.qtext ;
};
$scope.sendRequest = function () {
$scope.getQueryUrl(); // Gets the query url after adding all the parameters
alert($scope.queryUrl.Ebay);
$http.get($scope.queryUrl["Ebay"]).then(
function(response){
alert("success" + response.data );
},
function(response){
alert("error" + response.statusCode );
});
};
How this code should work :
It should create a formated Ebay query url, send it through HTTP GET request and sending back the response .
Note : $scope.qtext & dataAuth.EbayKeyApi are already assigned with their respective values .
What's the problem:
The problem is that using this Angularjs script, the code doesn't work, the alert "Error" is shown, and the response.statusCode is undefined .
But when I copy the formatted Ebay query link in Firefox it works perfectly and the XML response is shown .
The formatted Ebay query was generated using the script provided .
I think it's a header related problem .
$http has some default headers defined. $http sends Json payload and accepts Json as the response by default. Since you are dealing with XML you have to explicitly specify the accepted response type as XML using the header:
Accept: application/xml
Please use the following function with appropriate headers and you should get the response. Also, please look into any Cross Origin Resource Sharing (CORS) restrictions on the ebay API.
function getRequest(url) {
$http({
method: "GET",
url: url,
headers: {
'Content-Type': 'application/xml, text/xml',
'Accept': 'application/xml, text/plain, * / *'
}
})
.then(function (response) {
alert(response.data);
},
function (error) {
alert (error);
});
}
Thank you,
Soma.
I'm creating an Express route that calls the GitHub API with a ?callback=foo pattern added to the endpoint so that it will return the Link headers which I'll need to parse out the Link: header because it contains the link that I'll have to call to get the next page of the response.
The problem is that the response has the expected pattern, but when I try to create a function to tease out the meta and data portions of the function, they turn up undefined.
My code:
app.get('/populate', function(req, res, next) {
console.log('/populate route hit');
var token = "<something>";
var options = {
url: 'https://api.github.com/users?callback=resp',
headers: {
'User-Agent': 'Our-App',
'Authorization': 'token '+ token
}
};
api(options) // 'api' is request-promise module, makes http requests
.then(function(response) {
console.log(response); // Note 1
function resp(res) {
var meta = res.meta;
var data = res.data;
console.log('meta ', meta); // Note 2
console.log('data ', data);
}
resp(response);
Note 1: The response looks like:
/**/resp({"meta":{"X-RateLimit-Limit":"5000","X-RateLimit-Remaining":"4993",
"X-RateLimit-Reset":"1435297775","X-OAuth-Scopes":"public_repo, user:email",
"X-Accepted-OAuth-Scopes":"repo","Cache-Control":"private, max-age=60, s-maxage=60",
"Vary":"Accept, Authorization, Cookie, X-GitHub-OTP",
"ETag":"\"0cbbd180648a54f839a237b0302025db\"",
"X-GitHub-Media-Type":"github.v3; format=json",
"Link":[["https://api.github.com/users?callback=resp&since=46",
{"rel":"next"}],["https://api.github.com/users{?since}",
{"rel":"first"}]],"status":200},"data":[{"login":"mojombo","id":1,
...etc etc...
}]})
The response looks like it's been JSON.stringified but when I JSON.parse(response) it returns an error. I don't know how to access the deeply-embedded Link: headers and even the data, which looks like JSON, too.
Note 2 The res.meta and res.data log as undefined.
The response isn't JSON, it's JSONP. JSONP is a cross-domain mechanism for retrieving data. You don't use XHR (e.g., app.get) to request JSONP, you use a script tag. (Because XHR is limited by the Same Origin Policy; script tags aren't.)
If your call retrieving that data via XHR works, it means cross-domain XHR calls are allowed in your situation (the server supports Cross-Origin Resource Sharing with your page's origin, and the browser supports CORS). You can get JSON instead of JSONP by removing the ?callback=resp in the URL.
I am working on an angular.js project with one of my friends, and we are running into a specific CORS (cross origin request) issue. The server is a Microsoft ASP.NET restful API, and I am using angular.js with Node.js.
We enabled CORS on the server side, and are able to get responses for everything else, accept the user login, which we are using ASP.NET Identity with. We always get the same error which I will post bellow, as well as the POST from the Client side. So basically my question is, does any one have an idea on how to fix this? Thanks!
XMLHttpRequest cannot load http://lectioserver.azurewebsites.net/api/v1/accounts/login. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'localhost' is therefore not allowed access. The response had HTTP status code 400.
function login(username, password) {
var innerconfig = {
url: baseUrl + "/api/v1/accounts/login",
data: {
username: username,
password: password,
grant_type: "password"
},
method: "POST",
headers:
{
'Accept': 'text/json'
}
};
return $http(innerconfig).then(onSuccess, requestFailed);
function onSuccess(results) {
if (results && results.data) {
$rootScope.access_token = results.data.access_token;
return results.data;
}
return null;
}
}
Try to set the content-type in the headers, this might fix the issue
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
This usually happens because app that provides you token starts before CORS initiates.
Fixing it is very easy. You just need to go to IdentityConfig.cs and inside that there is function called as
public static ApplicationUserManager Create
(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
Insert this following line of code there
context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
This will enable CORS for Token request.
But problem is when we do this other normal requests will start throwing error since we have granted access origin * twice. Once in identiy and other in cors.
if you run into this error use this if statement on cors code in identity config you just pasted.
if(context.Request.ContentType == "text/plain")