CORS not working in a Open Keyless API?

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:

tyk@dev-env ~/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:

martin@a-dev-box ~> 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?:

martin@another-box ~> 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?

martin@devy-box ~> 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.