CORS not working in a Open Keyless API?

It’s a correct behaviour that CORS don’t work with an Open (keyless) API ?

It’s handled as part of the same bootstrap, you can see here (line 570) in a keyLess API Definition that the CORS handler is processed:

For reference, that handler basically does this:

So there’s no reason for it to not work.

Testing with curl -X OPTIONS httbin.org proxyed by Tyk with CORS disabled I get this:

< HTTP/1.1 200 OK

  • Server nginx is not blacklisted
    < Server: nginx
    < Date: Tue, 08 Mar 2016 11:34:37 GMT
    < Content-Type: text/html; charset=utf-8
    < Content-Length: 0
    < Connection: keep-alive
    < Access-Control-Allow-Credentials: true
    < Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
    < Access-Control-Allow-Origin: *
    < Access-Control-Max-Age: 3600
    < Allow: HEAD, OPTIONS, GET
    < X-Ratelimit-Limit: 0
    < X-Ratelimit-Remaining: 0
    < X-Ratelimit-Reset: 0

If enable CORS in Tyk with httpbin.org as Open API I get only this:

< HTTP/1.1 200 OK

  • Server nginx is not blacklisted
    < Server: nginx
    < Date: Tue, 08 Mar 2016 11:38:33 GMT
    < Content-Type: text/plain; charset=utf-8
    < Content-Length: 0
    < Connection: keep-alive
    < Vary: Origin
    < Vary: Access-Control-Request-Method
    < Vary: Access-Control-Request-Headers
    <

Httpbin adds it’s own CORS headers (curl it directly), it is not a good host to test against because the duplicated headers are what cause the Vary headers to show up. For Httpbin to work, just set Options pasthrough to enabled and disable CORS in Tyk.

I would suggest testing against a dumb server that doesn’t add any new headers, such as python built in.

Sorry, I make the assumption that Tyk don’t pass the OPTIONS call to the API if CORS it’s enabled.
And that the options pass thought check, enable and disable this calling to the backend API.
Please would you enlight me about this ?

Ok, Tyk is API Gateway, that means it manages every request and method, including OPTONS (who knows, your API might actually make use of this verb, it’s not reserved for CORS).

Now, if you have an upstream service that has a CORS implementation already, then Tyk should completely ignore OPTIONS methods as these are pre-flights sent by the browser. the reason is because as stated above, tyk would expect to throttle or manage that request in some way, including looking for auth headers (lets ignore the fact we’re talking about Open API here, this is the general use case).

So, in this scenario you just select “Options passthrough”, but do not enable CORS in Tyk, if you do, you’ll get odd behaviour as the middleware is loaded. This will cause Tyk to simply dump the options request upstream and reply with whatever the service responds with.

If your upstream service does not handle CORS natively, then you enable CORS in Tyk and disable Options pass through Now tyk is managing all CORS related headers and responses, including OPTIONS requests, which will actually terminate and be handled directly by our CORS middleware and never go upstream.

It’s either one, or the other, never both.

Thanks for the explanation… but I test de following

CORS enabled
Allow authentication unchecked
Options pass through unchecked
Allowed origins: empty
Allowed methods: empty
Allowed headers: empty
Exposed headers: empty

curl -v -X OPTIONS http://api:8080/security/login (api it’s the tyk server)
our API security it’s NOT called with the OPTIONS request (normal behaviour)
The response

OPTIONS /security/login HTTP/1.1
User-Agent: curl/7.35.0
Host: api:8080
Accept: /

< HTTP/1.1 200 OK
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Date: Tue, 08 Mar 2016 14:56:59 GMT
< Content-Length: 0
< Content-Type: text/plain; charset=utf-8

Sorry, but I’m lost here…

Looks like it…

First off, you need to set an allowed origin, even if it’s a wildcard *, you will also need to put some methods in there, otherwise there’s no point in CORS, it’s a security feature :wink:

So, according to the IETF CORS Spec, if you are sending an OPTIONS request, then you are doing a pre-flight CORS request, in which case, your request must include a Access-Control-Request-Method header

Additionally, according to 1.6 you will need to include an origin header, otherwise the request should fail

So, using a simple local server that returns a single JSON file (and also can’t be configured to handle CORS), we’re using a standard Python server:

[email protected] ~/test-api $ python -m SimpleHTTPServer 8000

And then constructing a request to a demo API on Tyk Cloud (Open) that is properly constructed as a pre-flight:

[email protected] ~> curl -v -X OPTIONS -H "Origin:http://localhost" -H "Access-Control-Request-Method: GET" http://tyk-inc-portal-test.cloud.tyk.io/cors-test/test.json
*   Trying 54.175.215.11...
* Connected to tyk-inc-portal-test.cloud.tyk.io (54.175.215.11) port 80 (#0)
> OPTIONS /cors-test/test.json HTTP/1.1
> Host: tyk-inc-portal-test.cloud.tyk.io
> User-Agent: curl/7.43.0
> Accept: */*
> Origin:http://localhost
> Access-Control-Request-Method: GET
>
< HTTP/1.1 200 OK
< Access-Control-Allow-Methods: GET
< Access-Control-Allow-Origin: http://localhost
< Access-Control-Max-Age: 24
< Cache-control: no-cache="set-cookie"
< Content-Type: text/plain; charset=utf-8
< Date: Tue, 08 Mar 2016 19:07:23 GMT
< Set-Cookie: AWSELB=A15351871EED3822C057D8653DCD7A0559A3D6489F9C7A44DD1761E93BB097F9EB797032EEEC31C283199D6E83B43D6BCC3F4FED507A84DCF4D895BD13534875D4DE01B750;PATH=/;MAX-AGE=180
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Content-Length: 0
< Connection: keep-alive
<
* Connection #0 to host tyk-inc-portal-test.cloud.tyk.io left intact

And there they are…

So, what if we do a regular request (not a pre-flight), just a GET against a resource on this API?:

[email protected] ~> curl -v -X GET -H "Origin:http://localhost" -H "Access-Control-Request-Method: GET" http://tyk-inc-portal-test.cloud.tyk.io/cors-test/test.json
*   Trying 54.175.215.11...
* Connected to tyk-inc-portal-test.cloud.tyk.io (54.175.215.11) port 80 (#0)
> GET /cors-test/test.json HTTP/1.1
> Host: tyk-inc-portal-test.cloud.tyk.io
> User-Agent: curl/7.43.0
> Accept: */*
> Origin:http://localhost
> Access-Control-Request-Method: GET
>
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: http://localhost
< Cache-control: no-cache="set-cookie"
< Content-Type: application/json
< Date: Tue, 08 Mar 2016 19:10:37 GMT
< Last-Modified: Tue, 08 Mar 2016 19:02:49 GMT
< Server: SimpleHTTP/0.6 Python/2.7.6
< Set-Cookie: AWSELB=A15351871EED3822C057D8653DCD7A0559A3D6489F9C7A44DD1761E93BB097F9EB797032EE2042D5795D5204974D5B39823F41C468ECB090C104546F869F4DA0E6D802025B;PATH=/;MAX-AGE=180
< Vary: Origin
< X-Ratelimit-Limit: 0
< X-Ratelimit-Remaining: 0
< X-Ratelimit-Reset: 0
< Content-Length: 10
< Connection: keep-alive
<
{"a":"b"}
* Connection #0 to host tyk-inc-portal-test.cloud.tyk.io left intact

What if we do a regular request without the Access-Control-Request-Method (since it is only required in pre-flights) according to the standard?

[email protected] ~> curl -v -X GET -H "Origin:http://localhost"  http://tyk-inc-portal-test.cloud.tyk.io/cors-test/test.json
*   Trying 54.175.215.11...
* Connected to tyk-inc-portal-test.cloud.tyk.io (54.175.215.11) port 80 (#0)
> GET /cors-test/test.json HTTP/1.1
> Host: tyk-inc-portal-test.cloud.tyk.io
> User-Agent: curl/7.43.0
> Accept: */*
> Origin:http://localhost
>
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: http://localhost
< Cache-control: no-cache="set-cookie"
< Content-Type: application/json
< Date: Tue, 08 Mar 2016 19:12:09 GMT
< Last-Modified: Tue, 08 Mar 2016 19:02:49 GMT
< Server: SimpleHTTP/0.6 Python/2.7.6
< Set-Cookie: AWSELB=A15351871EED3822C057D8653DCD7A0559A3D6489F9C7A44DD1761E93BB097F9EB797032EEEC31C283199D6E83B43D6BCC3F4FED507A84DCF4D895BD13534875D4DE01B750;PATH=/;MAX-AGE=180
< Vary: Origin
< X-Ratelimit-Limit: 0
< X-Ratelimit-Remaining: 0
< X-Ratelimit-Reset: 0
< Content-Length: 10
< Connection: keep-alive
<
{"a":"b"}
* Connection #0 to host tyk-inc-portal-test.cloud.tyk.io left intact

… That seems to work as expected.

So, there you have it, a working example of a pre-flight, regular (browsers insert extra headers), and minimal CORS requests against a standard Tyk installation returning the expected responses.

[EDIT] For reference, here’s the CORS setup for this test API:

[EDIT] Just for completeness, I thought I’d test a real AJAX request using JSFiddle, I modified an AJAX call here to point at this test API, now modified to also accept POST requests (otherwise we can’t force a pre-flight.

Now the python server doesn’t handle POST requests, so we get a 501 response, but that doesn’t matter, because the request and response in the browser gives us all the confirmation we need (taken from the network tab of the developer console):

Request:

POST /cors-test/test.json HTTP/1.1
Host: tyk-inc-portal-test.cloud.tyk.io
Connection: keep-alive
Content-Length: 0
Pragma: no-cache
Cache-Control: no-cache
Accept: */*
Origin: http://fiddle.jshell.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.103 Safari/537.36
Referer: http://fiddle.jshell.net/_display/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en;q=0.8,en-US;q=0.6,de;q=0.4,es;q=0.2

Response:

HTTP/1.1 501 Not Implemented
Access-Control-Allow-Origin: http://fiddle.jshell.net
Cache-control: no-cache="set-cookie"
Content-Type: text/html
Date: Tue, 08 Mar 2016 19:51:43 GMT
Server: SimpleHTTP/0.6 Python/2.7.6
Set-Cookie: AWSELB=A15351871EED3822C057D8653DCD7A0559A3D6489F2EDAC519C3F56B76311F575866272757EC31C283199D6E83B43D6BCC3F4FED507A84DCF4D895BD13534875D4DE01B750;PATH=/;MAX-AGE=180
Vary: Origin
X-Ratelimit-Limit: 0
X-Ratelimit-Remaining: 0
X-Ratelimit-Reset: 0
Content-Length: 217
Connection: keep-alive

And if we just use GET in the ajax request:

Request

GET /cors-test/test.json HTTP/1.1
Host: tyk-inc-portal-test.cloud.tyk.io
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: */*
Origin: http://fiddle.jshell.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.103 Safari/537.36
Referer: http://fiddle.jshell.net/_display/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-GB,en;q=0.8,en-US;q=0.6,de;q=0.4,es;q=0.2

And response:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://fiddle.jshell.net
Cache-control: no-cache="set-cookie"
Content-Type: application/json
Date: Tue, 08 Mar 2016 19:56:21 GMT
Last-Modified: Tue, 08 Mar 2016 19:02:49 GMT
Server: SimpleHTTP/0.6 Python/2.7.6
Set-Cookie: AWSELB=A15351871EED3822C057D8653DCD7A0559A3D6489F9C7A44DD1761E93BB097F9EB797032EEEC31C283199D6E83B43D6BCC3F4FED507A84DCF4D895BD13534875D4DE01B750;PATH=/;MAX-AGE=180
Vary: Origin
X-Ratelimit-Limit: 0
X-Ratelimit-Remaining: 0
X-Ratelimit-Reset: 0
Content-Length: 10
Connection: keep-alive

As you can see, the appropriate origin header is there. If I disable CORS, or unset the origin, these requests start throwing CORS errors, and quite rightly, because they should.

In the above browser examples, we needed to set a wildcard for the allowed origins in Tyk CORS settings, and we also needed to allow POST otherwise it wouldn’t work since we only allowed localhost earlier.

I can’t explain how embarrassed I feel right now… :pensive:
Thanks a lot for your exposition and for the time you use in it.
All things clear, but I find another culprit to my problems…
We are trying to use a custom header name with a ‘_’ character in it, and this breaks everything…
Using a ‘-’ instead make things working !
Thanks another time Martin for your support. :grin:

Lol. CORS is tricky.

Underscores are actually not allowed in headers, and some web servers actively strip them out.