I have an application (React SPA) that calls a bunch of servers on different subdomains of the application domain, i.e.:
- the web app sits at
foo.bar.com
, - and talks to
api.foo.bar.com
andmedia.foo.bar.com
.
When accessing api.foo.bar.com
, I get an error from the browser (be it Edge, Chrome, or Firefox) telling me that the origin (foo.bar.com
) is different from the value of the Access-Control-Allow-Origin
response header. However, by inspection of the response, they are the same:
(I unfortunately have to obfuscate the address.)
Those apps are hosted on Kubernetes; the ingress is NGINX, and it's is not providing CORS (cors-enabled annotation is false). Both applications (api
and media
) are Express apps, and both have the same CORS configuration allowing the specific origin.
I'm wondering if this has something to do with the redirect - the call to the media...
endpoint returns a redirect (302) whose Location is a api...
address.
Other than that, I have no clue what could be wrong. Something is, for sure, because all browsers agree that my request should be blocked (on account of the origin).
In all cases, I've checked the address multiple times for typos, ending forward-slashes, etc. I've called OPTIONS
on those endpoints with cURL and Postman, using all headers or just a few. They always answer the correct address.
Additional information, as requested:
Preflight request:
OPTIONS /media/1.0.0/rtsp/hls?feedUrl=https://live.monuv.com.br/a1/14298.stream/str27/chunklist.m3u8?m_hash=khV_hCnKG3nhaNCFaYZxBnoMz-99idQVHiQh80ADW78%3D HTTP/2
Host: media.aiXXXXXXXXXXXXXX.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Access-Control-Request-Method: GET
Access-Control-Request-Headers: feedurl
Referer: https://aiXXXXXXXXXXXXXXXX.com/
Origin: https://aiXXXXXXXXXXXXXXXX.com
DNT: 1
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Pragma: no-cache
Cache-Control: no-cache
TE: trailers
Preflight response:
HTTP/2 204 No Content
date: Fri, 08 Oct 2021 13:33:10 GMT
x-powered-by: Express
access-control-allow-origin: https://aiXXXXXXXXXXXXXXXXXX.com
vary: Origin
access-control-allow-credentials: true
access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE
access-control-allow-headers: Content-Type, feedUrl
strict-transport-security: max-age=15724800; includeSubDomains
X-Firefox-Spdy: h2
Request
The preflight passes, and the browsers starts a "flight" request:
GET /media/1.0.0/rtsp/hls?feedUrl=https://live.monuv.com.br/a1/14298.stream/str27/chunklist.m3u8?m_hash=khV_hCnKG3nhaNCFaYZxBnoMz-99idQVHiQh80ADW78%3D HTTP/2
Host: media.aiXXXXXXXXXXXXXXXXXXXXX.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
feedUrl: https://live.monuv.com.br/a1/14298.stream/str27/chunklist.m3u8?m_hash=khV_hCnKG3nhaNCFaYZxBnoMz-99idQVHiQh80ADW78=
Origin: https://aiXXXXXXXXXXXXXXXX.com
DNT: 1
Connection: keep-alive
Referer: https://aiXXXXXXXXXXXXXXXXX.com/
Cookie: ory_kratos_session=MTYzMzYzODY1OHxEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiXXXXXXXXXXXXYVc1bkRDSUFJSHBtUWxsaWFsVlJhWGRTVGxSMmIzZHRkbTFqYm5CUlRWVkdkelpPWkRoWnXXXTyqwgK-0Pe0qtZHjNhfU-YoASjg3istMZi672swQ==
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Pragma: no-cache
Cache-Control: no-cache
TE: trailers
Response
HTTP/2 302 Found
date: Fri, 08 Oct 2021 13:33:10 GMT
content-type: text/plain; charset=utf-8
content-length: 129
location: https://api.aiXXXXXXXXXXXXXXXXXX.com/media/1.0.0/hls/streams/19dd149d-f551-4093-b2aa-e5558388d545/hls.m3u8
x-powered-by: Express
access-control-allow-origin: https://aiXXXXXXXXXXXXXXXX.com
vary: Origin, Accept
access-control-allow-credentials: true
strict-transport-security: max-age=15724800; includeSubDomains
X-Firefox-Spdy: h2
At this response, the browser fails saying that the origin
don't match the access-control-allow-origin
.
(the first image was from Edge, since the log was more clear; this log is from Firefox)
CodePudding user response:
Problem
The error message—I'm using dummy URLs and origins below—from the browser can be a bit confusing:
Access to XMLHttpRequest at 'https://api.example.com/' (redirected from 'https://media.example.com/') from origin 'https://example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'https://example.com' that is not equal to the supplied origin.
The key here is that, as sideshowbarker hinted at in his comment, because your first preflighted request to https://media.example.com/
responds with a cross-origin redirect to https://api.example.com/
, the browser performs another whole CORS access-control check for that resource. However, because the redirect resulting from the first preflighted request happens to be cross-origin, the browser sets the origin of the second preflight request (which the error message refers to as the "supplied origin"), not as https://example.com
, but as the null
origin!
Here's a rundown of what is likely happening:
Because https://api.example.com
likely doesn't (and shouldn't!) allow the null
, the second access-control check fails and you get that annoying CORS error.
Solution
Resist the temptation to allow the null
origin on https://api.example.com/
, as doing so has serious security ramifications: it amount to voiding the protection that the Same-Origin Policy provides.
Instead, you should get rid of that redirect from https://api.example.com/
to https://api.example.com/
and make your frontend request the https://api.example.com/
resource directly.
Alternatively, if you cannot completely get rid of the redirect but you can change its destination, make it a same-origin redirect (from somewhere https://media.example.org
to elsewhere on https://media.example.org
).