I am working on SPA MEAN app, I was developing it against Apiary mock APIs, which has the following CORS headers set:
Access-Control-Allow-Methods → OPTIONS,GET,HEAD,POST,PUT,DELETE,TRACE,CONNECT
Access-Control-Allow-Origin → *
Access-Control-Max-Age → 10
It all works fine and angular can access it using $http angular service just fine. However after adding Stormpath Angular SDK all these requests fail with following error:
XMLHttpRequest cannot load https://xxx.apiary-mock.com/workshops?type=favourite. Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is ''. It must be 'true' to allow credentials. Origin 'http://localhost:8000' is therefore not allowed access.
I am trying to get to figure out why these requests are rejected and at what point these headers are added?
Thank you for finding this issue. The Stormpath Angular SDK does have an interceptor which sets the withCredentials: true flag for all requests. Please see the code here.
The intention is to ensure that our authentication cookies are always sent, even in a cross-domain situation. But I can see how this will be problematic if your Angular application is talking to other APIs that don't require cookies to be sent.
As a workaround, you can override our interceptor by simply adding another one:
angular.module('myapp', [
'stormpath',
'stormpath.templates'
]).config(function ($httpProvider) {
$httpProvider.interceptors.push(function() {
return {
request: function(config) {
config.withCredentials=false;
return config;
}
};
});
});
I've created an issue to discuss a better solution: https://github.com/stormpath/stormpath-sdk-angularjs/issues/72
Any time you have a SPA client served from one domain (e.g. localhost:8080) and you want that client to access an API on another domain (xxx.apiary-mock.com), the browser requires that the server domain add CORS headers correctly.
If the client and server domains are different, the browser's security model requires the server to indicate which client domains may access the server by setting the Access-Control-Allow-Origin response header (in addition to other relevant Access-Control-* headers).
I have how to work around it add these headers to apiary:
+ Response 200 (application/json)
+ Headers
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: OPTIONS,GET,HEAD,POST,PUT,DELETE,TRACE,CONNECT
Access-Control-Allow-Origin: http://localhost:8000
I am still trying to figure out where does Stormpath make those headers required though.
Related
I am building a web API. I found whenever I use Chrome to POST, GET to my API, there is always an OPTIONS request sent before the real request, which is quite annoying. Currently, I get the server to ignore any OPTIONS requests. Now my question is what's good to send an OPTIONS request to double the server's load? Is there any way to completely stop the browser from sending OPTIONS requests?
edit 2018-09-13: added some precisions about this pre-flight request and how to avoid it at the end of this reponse.
OPTIONS requests are what we call pre-flight requests in Cross-origin resource sharing (CORS).
They are necessary when you're making requests across different origins in specific situations.
This pre-flight request is made by some browsers as a safety measure to ensure that the request being done is trusted by the server.
Meaning the server understands that the method, origin and headers being sent on the request are safe to act upon.
Your server should not ignore but handle these requests whenever you're attempting to do cross origin requests.
A good resource can be found here http://enable-cors.org/
A way to handle these to get comfortable is to ensure that for any path with OPTIONS method the server sends a response with this header
Access-Control-Allow-Origin: *
This will tell the browser that the server is willing to answer requests from any origin.
For more information on how to add CORS support to your server see the following flowchart
http://www.html5rocks.com/static/images/cors_server_flowchart.png
edit 2018-09-13
CORS OPTIONS request is triggered only in somes cases, as explained in MDN docs:
Some requests don’t trigger a CORS preflight. Those are called “simple requests” in this article, though the Fetch spec (which defines CORS) doesn’t use that term. A request that doesn’t trigger a CORS preflight—a so-called “simple request”—is one that meets all the following conditions:
The only allowed methods are:
GET
HEAD
POST
Apart from the headers set automatically by the user agent (for example, Connection, User-Agent, or any of the other headers with names defined in the Fetch spec as a “forbidden header name”), the only headers which are allowed to be manually set are those which the Fetch spec defines as being a “CORS-safelisted request-header”, which are:
Accept
Accept-Language
Content-Language
Content-Type (but note the additional requirements below)
DPR
Downlink
Save-Data
Viewport-Width
Width
The only allowed values for the Content-Type header are:
application/x-www-form-urlencoded
multipart/form-data
text/plain
No event listeners are registered on any XMLHttpRequestUpload object used in the request; these are accessed using the XMLHttpRequest.upload property.
No ReadableStream object is used in the request.
Have gone through this issue, below is my conclusion to this issue and my solution.
According to the CORS strategy (highly recommend you read about it) You can't just force the browser to stop sending OPTIONS request if it thinks it needs to.
There are two ways you can work around it:
Make sure your request is a "simple request"
Set Access-Control-Max-Age for the OPTIONS request
Simple request
A simple cross-site request is one that meets all the following conditions:
The only allowed methods are:
GET
HEAD
POST
Apart from the headers set automatically by the user agent (e.g. Connection, User-Agent, etc.), the only headers which are allowed to be manually set are:
Accept
Accept-Language
Content-Language
Content-Type
The only allowed values for the Content-Type header are:
application/x-www-form-urlencoded
multipart/form-data
text/plain
A simple request will not cause a pre-flight OPTIONS request.
Set a cache for the OPTIONS check
You can set a Access-Control-Max-Age for the OPTIONS request, so that it will not check the permission again until it is expired.
Access-Control-Max-Age gives the value in seconds for how long the response to the preflight request can be cached for without sending another preflight request.
Limitation Noted
For Chrome, the maximum seconds for Access-Control-Max-Age is 600 which is 10 minutes, according to chrome source code
Access-Control-Max-Age only works for one resource every time, for example, GET requests with same URL path but different queries will be treated as different resources. So the request to the second resource will still trigger a preflight request.
Please refer this answer on the actual need for pre-flighted OPTIONS request: CORS - What is the motivation behind introducing preflight requests?
To disable the OPTIONS request, below conditions must be satisfied for ajax request:
Request does not set custom HTTP headers like 'application/xml' or 'application/json' etc
The request method has to be one of GET, HEAD or POST. If POST, content type should be one of application/x-www-form-urlencoded, multipart/form-data, or text/plain
Reference:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
When you have the debug console open and the Disable Cache option turned on, preflight requests will always be sent (i.e. before each and every request). if you don't disable the cache, a pre-flight request will be sent only once (per server)
Yes it's possible to avoid options request. Options request is a preflight request when you send (post) any data to another domain. It's a browser security issue. But we can use another technology: iframe transport layer. I strongly recommend you forget about any CORS configuration and use readymade solution and it will work anywhere.
Take a look here:
https://github.com/jpillora/xdomain
And working example:
http://jpillora.com/xdomain/
For a developer who understands the reason it exists but needs to access an API that doesn't handle OPTIONS calls without auth, I need a temporary answer so I can develop locally until the API owner adds proper SPA CORS support or I get a proxy API up and running.
I found you can disable CORS in Safari and Chrome on a Mac.
Disable same origin policy in Chrome
Chrome: Quit Chrome, open an terminal and paste this command: open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir
Safari: Disabling same-origin policy in Safari
If you want to disable the same-origin policy on Safari (I have 9.1.1), then you only need to enable the developer menu, and select "Disable Cross-Origin Restrictions" from the develop menu.
As mentioned in previous posts already, OPTIONS requests are there for a reason. If you have an issue with large response times from your server (e.g. overseas connection) you can also have your browser cache the preflight requests.
Have your server reply with the Access-Control-Max-Age header and for requests that go to the same endpoint the preflight request will have been cached and not occur anymore.
I have solved this problem like.
if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && ENV == 'devel') {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: X-Requested-With');
header("HTTP/1.1 200 OK");
die();
}
It is only for development. With this I am waiting 9ms and 500ms and not 8s and 500ms. I can do that because production JS app will be on the same machine as production so there will be no OPTIONS but development is my local.
You can't but you could avoid CORS using JSONP.
After spending a whole day and a half trying to work through a similar problem I found it had to do with IIS.
My Web API project was set up as follows:
// WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
//...
}
I did not have CORS specific config options in the web.config > system.webServer node like I have seen in so many posts
No CORS specific code in the global.asax or in the controller as a decorator
The problem was the app pool settings.
The managed pipeline mode was set to classic (changed it to integrated) and the Identity was set to Network Service (changed it to ApplicationPoolIdentity)
Changing those settings (and refreshing the app pool) fixed it for me.
OPTIONS request is a feature of web browsers, so it's not easy to disable it. But I found a way to redirect it away with proxy. It's useful in case that the service endpoint just cannot handle CORS/OPTIONS yet, maybe still under development, or mal-configured.
Steps:
Setup a reverse proxy for such requests with tools of choice (nginx, YARP, ...)
Create an endpoint just to handle the OPTIONS request. It might be easier to create a normal empty endpoint, and make sure it handles CORS well.
Configure two sets of rules for the proxy. One is to route all OPTIONS requests to the dummy endpoint above. Another to route all other requests to actual endpoint in question.
Update the web site to use proxy instead.
Basically this approach is to cheat browser that OPTIONS request works. Considering CORS is not to enhance security, but to relax the same-origin policy, I hope this trick could work for a while. :)
you can also use a API Manager (like Open Sources Gravitee.io) to prevent CORS issues between frontend app and backend services by manipulating headers in preflight.
Header used in response to a preflight request to indicate which HTTP headers can be used when making the actual request :
content-type
access-control-allow-header
authorization
x-requested-with
and specify the "allow-origin" = localhost:4200 for example
One solution I have used in the past - lets say your site is on mydomain.com, and you need to make an ajax request to foreigndomain.com
Configure an IIS rewrite from your domain to the foreign domain - e.g.
<rewrite>
<rules>
<rule name="ForeignRewrite" stopProcessing="true">
<match url="^api/v1/(.*)$" />
<action type="Rewrite" url="https://foreigndomain.com/{R:1}" />
</rule>
</rules>
</rewrite>
on your mydomain.com site - you can then make a same origin request, and there's no need for any options request :)
It can be solved in case of use of a proxy that intercept the request and write the appropriate headers.
In the particular case of Varnish these would be the rules:
if (req.http.host == "CUSTOM_URL" ) {
set resp.http.Access-Control-Allow-Origin = "*";
if (req.method == "OPTIONS") {
set resp.http.Access-Control-Max-Age = "1728000";
set resp.http.Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, PATCH, OPTIONS";
set resp.http.Access-Control-Allow-Headers = "Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since";
set resp.http.Content-Length = "0";
set resp.http.Content-Type = "text/plain charset=UTF-8";
set resp.status = 204;
}
}
What worked for me was to import "github.com/gorilla/handlers" and then use it this way:
router := mux.NewRouter()
router.HandleFunc("/config", getConfig).Methods("GET")
router.HandleFunc("/config/emcServer", createEmcServers).Methods("POST")
headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type"})
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
log.Fatal(http.ListenAndServe(":" + webServicePort, handlers.CORS(originsOk, headersOk, methodsOk)(router)))
As soon as I executed an Ajax POST request and attaching JSON data to it, Chrome would always add the Content-Type header which was not in my previous AllowedHeaders config.
I was testing out the new API Gateway to secure a cloud function for my React application. So far the process has been much nicer than the previous alternatives, but I am currently getting CORS errors when trying to reach out to my API Gateway from my React app. I am setting the CORS headers correctly in my Cloud Function, but I have no known way of doing the same on the API Gateway endpoint. I am using Postman to test a request to the gateway endpoint and everything is working great, so it is just when I request from my React app.
Error: "Access to fetch at 'https://my-gateway-a12bcd345e67f89g0h.uc.gateway.dev/hello?key=example' from origin 'https://example.netlify.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled."
Would love some insight into this issue. Thanks!
This is not yet supported, however, there is a temporary workaround to get this working. You should add options to the paths in your openapi.yaml. Additionally, both get and options operations should point to the same cloud function, since the options request then acts as a warmup request for the cloud function. This is the most efficient setup, in terms of latency. Here is a simplified example:
paths:
/helloworld:
get:
operationId: getHelloWorld
x-google-backend:
address: $CLOUD_FUNCTION_ADDRESS
responses:
'200':
description: A successful response
options:
operationId: corsHelloWorld
x-google-backend:
address: $CLOUD_FUNCTION_ADDRESS
responses:
'200':
description: A successful response
Then, in your cloud function backend, you must also handle the preflight request (source). The Google documentation also provides an example with authentication, which has some additional headers. Here is an example without authentication:
def cors_enabled_function(request):
# For more information about CORS and CORS preflight requests, see
# https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
# for more information.
# Set CORS headers for the preflight request
if request.method == 'OPTIONS':
# Allows GET requests from any origin with the Content-Type
# header and caches preflight response for an 3600s
headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '3600'
}
return ('', 204, headers)
# Set CORS headers for the main request
headers = {
'Access-Control-Allow-Origin': '*'
}
return ('Hello World!', 200, headers)
Note: the downside of API gateway not managing preflight requests in a proper manner results in a penalty of running the cloud function twice. But your second request should always be very fast since the first request acts as a warmup request.
Turns out that API Gateway does not currently have CORS support.
Reference.
Solution
Here is the solution. It is just as user14982714 stated. Add a host and x-google-endpoints to your oepnapi.yaml file at the top level:
host: my-cool-api.endpoints.my-project-id.cloud.goog
x-google-endpoints:
- name: my-cool-api.endpoints.my-project-id.cloud.goog
allowCors: True
However, be sure to replace my-cool-api.endpoints.my-project-id.cloud.goog with your API Managed Service URL. This can be found in your google cloud console under the API Gateway API here:
I covered the start to my endpoint name for privacy, however, yours should also end with .cloud.goog. If you haven't deployed a configuration yet, deploy it without the x-google-endpoints and host, then update it to include both. (To update your configuration go to API Gateway -> Your API -> Gateways Tab -> Your Gateway -> Edit- > Change API Config -> Create New)
Explanation
Now to explain why this works with Google Cloud API Gateway. The API Gateway uses endpoints under the hood. Most people don't know this is the case, however, after giving up on the API Gateway and moving back to Endpoints I noticed that my API Gateways were listed under the endpoint services. They do not show in the UI, but with the gcloud CLI, run this command gcloud endpoints services list and you should see your API Gateways. Crazy! But Google does this a lot.
So knowing this I tried adding allowCors: true to the x-google-endpoints and viola. It worked. I hope this helps someone out there.
swagger: "2.0"
host: "my-cool-api.endpoints.my-project-id.cloud.goog"
x-google-endpoints:
- name: "my-cool-api.endpoints.my-project-id.cloud.goog"
allowCors: True
Note: host and name should have the same API endpoint name
Configuring these lines in your config file enables CORS for API GATEWAY
[Reference][1]
[1]: https://cloud.google.com/endpoints/docs/openapi/support-cors#:~:text=CORS%20(Cross%2Dorigin%20resource%20sharing,would%20prevent%20cross%2Dorigin%20requests.
I had the same issue and solve it with a load balancer (originally used to add a custom domain to my API gateway).
I use my load balancer to add the missing header into the response.
You just need to add the "Access-Control-Origin" header:
Allow all
Access-Control-Origin:'*'
Allow a specific origin
Access-Control-Allow-Origin: http://example.com:8080
You can find the instructions here GCP - Creating custom headers.
If you do not have a load balancer implemented,
you can follow this tutorial to implement a new one Google API Gateway, Load Balancer and Content Delivery Network.
You can find more information regarding CORS at https://www.w3.org/wiki/CORS_Enabled.
I had similar issue with other API so I am not sure the same will work in your case but you can try - in react app when fetching the data, lets say with axios you can try
axios.post('http://localhost:3003/signup',this.data,{headers:{'Access-Control-
Allow-Origin':'*','Content-Type': 'application/json'}})
on the backend side - try this -
let cors=require('./cors')
app.options('*', cors());
It works in my case , hope it will help you.
I have tried all things, use CORS plugin. disable web-security in chrome.
The response is coming in POSTMAN but not able to fetch it in $http.
$http({
url: "https://interview-api-staging.bytemark.co/books",
method: 'GET',
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET,OPTIONS,PUT,DELETE',
'Access-Control-Allow-Headers': 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since'
}
}).then(function(d) {
console.log(d);
});
Client has nothing to do with it. With a CORS header, you're telling the client which other servers do I trust. Those then can share your resources and client won't mind.
For example if you have two domains you tell the client so let your resources be used by your second website, you don't say I trust you as a client.
So you're protecting the server, not client. You don't want AJAX API Endpoints to be accessible by scripts hosted anywhere in the world.
A client has nothing to gain/lose from this. It's only a protection for servers because using AJAX all the URLs are clearly visible to anyone and had it been not for this protection, anybody could go-ahead run their front end using your API, only servers have to lose from this so they get to decide who can use their resources.
source.
As mentioned you don't need to do any cors related stuff in the front-end. Make sure the cors headers are sent from the backend in its response headers.
It is the server that has to protect itself so they have to tell some rules to the client which client will follow. By default, the client will accept everything.
Use CORS in your backend. Otherwise, you can check out Allow-Control-Allow-Origin: * in the Chrome Web Store, use chrome extension.
when you trying to hit through the angular app you need to turn on that extension.
otherwise you need to active CORS in your backend application
I'm using Stormpath and I'd like to separate my API from the client code (Angular, using Stormpath's angular-sdk). This means the API will be on a different sub-domain than the client. As soon as I did this, I hit the error;
A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true
I fixed this by adding the specific domains to my express CORS configuration by setting Access-Control-Allow-Origin, but now the API just won't respond to hits from the browser.
So I'm a little confused, I'd prefer the API to be open but still authenticate using Stormpath but maybe thats not possible?
The Stormpath AngularJS SDK has an example application that shows you how to get CORS working with AngularJS and Express.
https://github.com/stormpath/stormpath-sdk-angularjs/tree/master/example/cors-app
The code you're looking for is in server.js, starting at line 22.
var cors = require('cors');
...
/*
The apiService is your API that you want to protect with
Stormpath. We use the CORS module to whitelist the website
domain, thus allowing it to communicate with the API domain
*/
var apiService = express();
apiService.use(cors({
origin: websiteDomain,
credentials: true
}));
apiService.use(stormpath.init(apiService,{ }));
I want to call web service from another domain using angular2 typescript.
i am using following code to call api
var req_dict = {
method: RequestMethod.Get,
url: 'http//127.0.0.1:5000/users/',
headers: this.head
}
var options = new RequestOptions(req_dict);
var req = new Request(options);
this.http.request(req).subscribe({});
I hosted my application in http//127.0.0.1:8000. I want to use api from port 5000. when i inspected console , request is going to
http://127.0.0.1:8000/http//127.0.0.1:5000/users/
i want to call just http//127.0.0.1:5000/users/. how can call api by absolute url in angular2 ?
In fact, it's not an issue of TypeScript or Angular2 but a CORS issue. These blog posts could help you to understand in details how this works:
http://restlet.com/blog/2015/12/15/understanding-and-using-cors/
http://restlet.com/blog/2016/09/27/how-to-fix-cors-problems/
To be short, in the case of cross domain request, the browser automatically adds an Origin header in the request. There are two cases:
Simple requests. This use case applies if we use HTTP GET, HEAD and POST methods. In the case of POST methods, only content types with the following values are supported: text/plain, application/x-www-form-urlencoded and multipart/form-data.
Preflighted requests. When the "simple requests" use case doesn't apply, a first request (with the HTTP OPTIONS method) is made to check what can be done in the context of cross-domain requests.
So in fact most of work must be done on the server side to return the CORS headers. The main one is the Access-Control-Allow-Origin one.
200 OK HTTP/1.1
(...)
Access-Control-Allow-Origin: *
To debug such issues, you can use developer tools within browsers (Network tab).
Regarding Angular2, simply use the Http object like any other requests (same domain for example):
return this.http.get('https://angular2.apispark.net/v1/companies/')
.map(res => res.json()).subscribe(
...
);
Hope it helps you,
Thierry