-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Discussed in #2018
Originally posted by sm-Fifteen January 27, 2023
The way SessionMiddleware currently works is that it encodes session data in a signed base64 object with timestamp, in a format not entirely dissimilar to (but incompatible with) JWT. Because of the way it works, being run right before the headers for an HTTP response are sent, and because it doesn't perform many checks besides whether or not there is session data to encode, it will try to update the session cookie whenever a route is called. This is somewhat wasteful, as session data is not usually updated on every request, but it leads to interesting issues as well.
I have a FastAPI application where login is handeled via a session cookie with a separate login process. Several routes are unlocked based on whether or not the cookie is present and based on the user authorizations recorded in said session cookie. When the frontend fetches API data, if the user is logged in, every response contains a new Set-Cookie
header, which Firefox ignores, but Chrome acknowledges. None of the API routes alter session data, so the cookie value is always the same, save for the timestamp and signature at the end. This actually runs into an interesting Chrome issue where responses to fetch requests that arrive after the page has been navigated away from still alter the session cookie, even if if the new page changed the session data on load. This means I have this strange, Chrome-exclusive, race-condition-y bug that matches authlib/authlib#334, where the session state data setup by the Oauth flow before redirecting to the third party login form may get clobbered if some /api/slow_route
query was pending when the user clicked the login button.
It's unclear if it's actually a chrome bug or unintended behavior from everything working as intended. If Starlette wasn't updating cookies for every request, and the Set-Cookie
was actually important here, it's difficult to argue whether or not Chrome should have ignored it.
See here the Starlette test case from the Chromium bug report, which doesn't use SessionMiddleware, but shows the Chrome behavior in action.
from starlette.applications import Starlette
from starlette.responses import HTMLResponse, RedirectResponse, JSONResponse, PlainTextResponse
from starlette.routing import Route
import uvicorn
import datetime
from asyncio import sleep
html_content = """
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="2;url=/set_cookie_and_redirect" />
<script>fetch("/set_cookie_on_fetch")</script>
</head>
<body>
Calling slow route via fetch() before changing page.
</body>
</html>
"""
async def homepage(request):
return HTMLResponse(html_content)
async def set_cookie_on_fetch(request):
res = JSONResponse({'hello':'world'})
await sleep(5)
res.set_cookie("my_cookie", "a__" + datetime.datetime.now().isoformat())
return res
async def set_cookie_and_redirect(request):
res = RedirectResponse("/final")
res.set_cookie("my_cookie", "b__" + datetime.datetime.now().isoformat())
return res
async def final(request):
cookie_val = request.cookies.get('my_cookie')
cookie_is_b = cookie_val.partition("__")[0] == 'b'
if (cookie_is_b):
res = PlainTextResponse("Cookie value is \"" + cookie_val + "\", all is well. Try refreshing the page.", 200)
else:
res = PlainTextResponse("Cookie value is \"" + cookie_val + "\", it shouldn't be.", 400)
return res
routes = [
Route('/', homepage),
Route('/set_cookie_on_fetch', set_cookie_on_fetch),
Route('/set_cookie_and_redirect', set_cookie_and_redirect),
Route('/final', final),
]
app = Starlette(debug=True, routes=routes)
uvicorn.run(app, host="0.0.0.0", port=8000)
```</div>
<!-- POLAR PLEDGE BADGE START -->
> [!IMPORTANT]
> - We're using [Polar.sh](https://polar.sh/encode) so you can upvote and help fund this issue.
> - We receive the funding once the issue is completed & confirmed by you.
> - Thank you in advance for helping prioritize & fund our backlog.
<a href="https://polar.sh/encode/starlette/issues/2019">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/encode/starlette/issues/2019/pledge.svg?darkmode=1">
<img alt="Fund with Polar" src="https://polar.sh/api/github/encode/starlette/issues/2019/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->