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

Share session data across mounted routes #1351

Closed
wants to merge 8 commits into from
2 changes: 1 addition & 1 deletion starlette/middleware/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:

async def send_wrapper(message: Message) -> None:
if message["type"] == "http.response.start":
path = scope.get("root_path", "") or "/"
path = scope.get("session_cookie_path", "") or "/"
if scope["session"]:
# We have session data to persist.
data = b64encode(json.dumps(scope["session"]).encode("utf-8"))
Expand Down
13 changes: 13 additions & 0 deletions starlette/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ def __init__(
self.app: ASGIApp = app
else:
self.app = Router(routes=routes)
self._should_share_cookies = routes is not None
self.name = name
self.path_regex, self.path_format, self.param_convertors = compile_path(
self.path + "/{path:path}"
Expand All @@ -371,6 +372,18 @@ def matches(self, scope: Scope) -> typing.Tuple[Match, Scope]:
"path_params": path_params,
"app_root_path": scope.get("app_root_path", root_path),
"root_path": root_path + matched_path,
"session_cookie_path": (
# NOTE: if mounting a list of routes, rather than a fully
# separate ASGI app, session data modified by these routes
# should apply to the parent path.
# This allows e.g. mounting auth routes and have their
# session modifications be available to the parent app,
# for use in e.g. permission checks in neighboring routes.
# See: https://github.com/encode/starlette/issues/1261
root_path
if self._should_share_cookies
else root_path + matched_path
),
"path": remaining_path,
"endpoint": self.app,
}
Expand Down
21 changes: 20 additions & 1 deletion tests/middleware/test_session.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import re

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import JSONResponse
from starlette.routing import Mount, Route


def view_session(request):
Expand Down Expand Up @@ -104,7 +106,7 @@ def test_secure_session(test_client_factory):
assert response.json() == {"session": {}}


def test_session_cookie_subpath(test_client_factory):
def test_session_mounted_app(test_client_factory):
app = create_app()
second_app = create_app()
second_app.add_middleware(SessionMiddleware, secret_key="example")
Expand All @@ -118,6 +120,23 @@ def test_session_cookie_subpath(test_client_factory):
assert cookie_path == "/second_app"


def test_session_mounted_routes(test_client_factory):
auth_routes = [
Route("/login", update_session, methods=["POST"]),
]

app = Starlette(
routes=[Mount("/auth", routes=auth_routes)],
middleware=[Middleware(SessionMiddleware, secret_key="example")],
)

client = test_client_factory(app, base_url="http://testserver")
response = client.post("/auth/login", json={"some": "data"})
cookie = response.headers["set-cookie"]
cookie_path = re.search(r"; path=(\S+);", cookie).groups()[0]
assert cookie_path == "/"


def test_invalid_session_cookie(test_client_factory):
app = create_app()
app.add_middleware(SessionMiddleware, secret_key="example")
Expand Down