Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: cors middleware mirrors origin in case no initial cookie is present #2675

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions starlette/middleware/cors.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ async def send(self, message: Message, send: Send, request_headers: Headers) ->
headers = MutableHeaders(scope=message)
headers.update(self.simple_headers)
origin = request_headers["Origin"]
has_cookie = "cookie" in request_headers
has_cookie = "cookie" in request_headers or "set-cookie" in headers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this check should be here. See comment on the tests.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also tried checking the django implementation (although I am not expert on these things). It looks like they basically never check the wether a cookie was there or shall be set:

if conf.CORS_ALLOW_ALL_ORIGINS and not conf.CORS_ALLOW_CREDENTIALS:

From what I read, I would suggest removing line 161 completely and repalce 165 with

if self.allow_all_origins and self.allow_credentails:

What do you think @Kludex ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kludex
I checked some documentation and stack overflow again and I think the mirroring of the origin is not working properly in this middleware.

Mirroring the origin is required for various purposes if allow credentials is true, not only cookies (https://stackoverflow.com/questions/25064158/how-to-make-get-cors-request-with-authorization-header & https://stackoverflow.com/questions/40663641/cors-with-client-https-certificates)

Basically, whenever credentails are used the the origin has to match the host of the client call otherwise the browser will block all things that require credentials such as cookies, authorization headers, certificates and so.

Django also solves it this way to basically always mirror the origin when allow credentials is true (https://github.com/adamchainz/django-cors-headers/blob/0c34907bca897da9934bfd7da40b38ae89da44d5/src/corsheaders/middleware.py#L115) since it otherwise will never work.

Unfortunately many tests will fail...


# If request includes any cookie headers, then we must respond
# If request or response includes any cookie headers, then we must respond
# with the specific origin instead of '*'.
if self.allow_all_origins and has_cookie:
self.allow_explicit_origin(headers, origin)
Expand Down
23 changes: 23 additions & 0 deletions tests/middleware/test_cors.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,29 @@ def homepage(request: Request) -> PlainTextResponse:
assert "access-control-allow-credentials" not in response.headers


def test_cors_credentialed_requests_return_specific_origin_without_initial_cookie(
test_client_factory: TestClientFactory,
) -> None:
def homepage(request: Request) -> PlainTextResponse:
response = PlainTextResponse("Homepage", status_code=200)
response.set_cookie("mycookie", "myvalue", path=None)
return response

app = Starlette(
routes=[Route("/", endpoint=homepage)],
middleware=[Middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=["*"])],
)
client = test_client_factory(app)

# Test credentialed request
headers = {"Origin": "https://example.org"}
response = client.get("/", headers=headers)
assert response.status_code == 200
assert response.text == "Homepage"
assert response.headers["access-control-allow-origin"] == "https://example.org"
assert "access-control-allow-credentials" in response.headers


def test_cors_vary_header_defaults_to_origin(
test_client_factory: TestClientFactory,
) -> None:
Expand Down