I am developing a stateless FastAPI app. The authentication is handled via Google OAuth (openid
email profile scope). I am using Authlib
and it uses SessionMiddleware
(request.session
) to store temporary codes and states. Now, I want to restrict access to some endpoints with a token. For this purpose I am creating a JWT with the user email from the ID token I get from Google, storing it in the cookies on the client, and validating it every time there is a request to a protected endpoint. It does exactly what I want.
Since I am using SessionMiddleware
anyways I thought about a different approach. I can just store the user email in the session from SessionMiddleware
. It automatically creates a session cookie and stores it on the client side. To restrict the access I can get the user from the session. In the background, Starlette validates the cookie automatically. I already tested it and it also works fine and saves me a lot of JWT code.
My question is: Does SessionMiddleware
store the session data in the cookie on the client or does it just encode an ID and the session data is saved on the server?
From my research and own implementation I get mixed answers. If I restart my app, I still can access the session data using the token. This hints towards the whole process being stateless. And if I check Starlette's source code I also think the session data gets directly encoded in the session cookie. But everywhere else I look I see people saying that SessionMiddleware
operates stateful.
In the default config, SessionMiddleware
stores session data by encoding it directly into the client's cookie.
But if needed, you can store session data on server-side and only keep an ID in the cookie, using PyPI's aioredis
(cf. https://pypi.org/project/aioredis/).
It looks something like this:
from starlette.middleware.sessions import SessionMiddleware
from fastapi import FastAPI, Request, Depends
import aioredis
import uuid
app = FastAPI()
redis = aioredis.from_url("redis://localhost")
app.add_middleware(SessionMiddleware, secret_key="yoursecretkey")
async def get_session_data(session_id: str):
session_data = await redis.get(session_id)
return session_data if session_data else {}
@app.get("/set-session")
async def set_session(request: Request):
session_id = str(uuid.uuid4())
await redis.set(session_id, {'your_email': 'yourmail@somedomain.com'})
response = {"message": "Session data set"}
response.set_cookie(key="session_id", value=session_id)
return response
@app.get("/get-session")
async def get_session(request: Request):
session_id = request.cookies.get("session_id")
session_data = await get_session_data(session_id)
return {"your_email": session_data.get('your_email')}
Hope this helps.