diff --git a/docs/middleware.md b/docs/middleware.md index a91342744..92ac5886a 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -106,6 +106,8 @@ The following arguments are supported: * `max_age` - Session expiry time in seconds. Defaults to 2 weeks. If set to `None` then the cookie will last as long as the browser session. * `same_site` - SameSite flag prevents the browser from sending session cookie along with cross-site requests. Defaults to `'lax'`. * `https_only` - Indicate that Secure flag should be set (can be used with HTTPS only). Defaults to `False`. +* `domain` - Domain of the cookie used to share cookie between subdomains or cross-domains. The browser defaults the domain to the same host that set the cookie, excluding subdomains [refrence](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#domain_attribute). + ## HTTPSRedirectMiddleware diff --git a/starlette/middleware/sessions.py b/starlette/middleware/sessions.py index 74e57352f..1093717b4 100644 --- a/starlette/middleware/sessions.py +++ b/starlette/middleware/sessions.py @@ -20,6 +20,7 @@ def __init__( path: str = "/", same_site: typing.Literal["lax", "strict", "none"] = "lax", https_only: bool = False, + domain: typing.Optional[str] = None, ) -> None: self.app = app self.signer = itsdangerous.TimestampSigner(str(secret_key)) @@ -29,6 +30,8 @@ def __init__( self.security_flags = "httponly; samesite=" + same_site if https_only: # Secure flag can be used with HTTPS only self.security_flags += "; secure" + if domain is not None: + self.security_flags += f"; domain={domain}" async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if scope["type"] not in ("http", "websocket"): # pragma: no cover diff --git a/tests/middleware/test_session.py b/tests/middleware/test_session.py index 3f43506c4..8500f7b54 100644 --- a/tests/middleware/test_session.py +++ b/tests/middleware/test_session.py @@ -178,3 +178,27 @@ def test_session_cookie(test_client_factory): client.cookies.delete("session") response = client.get("/view_session") assert response.json() == {"session": {}} + + +def test_domain_cookie(test_client_factory): + app = Starlette( + routes=[ + Route("/view_session", endpoint=view_session), + Route("/update_session", endpoint=update_session, methods=["POST"]), + ], + middleware=[ + Middleware(SessionMiddleware, secret_key="example", domain=".example.com") + ], + ) + client: TestClient = test_client_factory(app) + + response = client.post("/update_session", json={"some": "data"}) + assert response.json() == {"session": {"some": "data"}} + + # check cookie max-age + set_cookie = response.headers["set-cookie"] + assert "domain=.example.com" in set_cookie + + client.cookies.delete("session") + response = client.get("/view_session") + assert response.json() == {"session": {}}