question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[FEATURE] First-class session support in FastAPI

See original GitHub issue

Is your feature request related to a problem

All of the security schemas currently supported by FastAPI rely on some sort of “client-server synergy” , where, for instance, the client is expected to know and remember an OAuth token or the user credentials to be sent via headers. This works fairly well for single-page applications, but if you need to integrate authentication to an app that uses templates, keeping track of that authentication data becomes a challenge. Most applications would use server-side sessions to deal with this, but FastAPI doesn’t really have a system to deal with sessions right now.

Describe alternatives you’ve considered

Using Starlette’s SessionMiddleware

While Starlette’s SessionMiddleware is mentionned a number of times in the FastAPI documentation, it does not integrate very well with the framework itself . What it does is that it adds a request.session dict on the Request object that lets the backend store and retreive information from it, and just before the response get sent, that dict is serialized, combined to a timestamp, signed, converted into base 64 and appended as a cookie. The client is then expected to send theat cookie back so that the server so that information can be decoded and used. This is what the Django doc describes as the cookie-based approach.

The problem with all this is that the entire process happens outside of what FastAPI can handle, and therefore does not appear in the generated OpenAPI documentation as an authentication schema.

Having read the source for that middleware and the documentation for itsdangerous, I also understand that this kind of “session blob” authentication method isn’t really supported by OpenAPI, since all supported auth methods are expected to use constants to handle authentication.

The solution you would like

Ideally, I would like to see FastAPI adding some kind of SessionCookie[T] class to fastapi.security, that would register a cookie-based API key authentication method (which is what Swagger reccomands, since sessions are out of scope for the OpenAPI spec). Those “API keys” would be session tokens, much like the It should also register that routes that depend on that security schema may reply with a Set-Cookie header.

The question of how that data would be persisted afterwards is an open one. Having a one-size-fits-all implementation as the only one available could be constraining, so there’s always the option of a fastapi.security.sessions namespace containing things like MemorySessionStorage, DatabaseSessionStorage, FileSessionStorage and so on.

Additional context

Maybe something like this?

from fastapi import Depends, FastAPI
from fastapi.security.sessions import SessionCookie, MemorySessionStorage
from pydantic import BaseModel
from datetime import timedelta

app = FastAPI()

class SessData(BaseModel):
    # BaseModel so it can be serialized and stored properly
    uname: str

security = SessionCookie[SessData](
    name='fastapi_sess',
    expires=timedelta(hours=1).
    backend=MemorySessionStorage(),
    auto_error=False
)

@app.get('/secure/rm_rf/{path:path}')
def secure_thing(path: str, session: Optional[SessData] = Depends(security)):
    if session is not None and session.uname == 'root':
        # ...

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:96
  • Comments:26 (12 by maintainers)

github_iconTop GitHub Comments

43reactions
tiangolocommented, Feb 12, 2020

It’s already in place. More or less like the rest of the security tools. And it’s compatible with the rest of the parts, integrated with OpenAPI (as possible), but probably most importantly, with dependencies.

It’s just not properly documented yet. 😞

But still, it works 🚀 e.g.

from fastapi import FastAPI, Form, HTTPException, Depends
from fastapi.security import APIKeyCookie
from starlette.responses import Response, HTMLResponse
from starlette import status
from jose import jwt


app = FastAPI()

cookie_sec = APIKeyCookie(name="session")

secret_key = "someactualsecret"

users = {"dmontagu": {"password": "secret1"}, "tiangolo": {"password": "secret2"}}


def get_current_user(session: str = Depends(cookie_sec)):
    try:
        payload = jwt.decode(session, secret_key)
        user = users[payload["sub"]]
        return user
    except Exception:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="Invalid authentication"
        )


@app.get("/login")
def login_page():
    return HTMLResponse(
        """
        <form action="/login" method="post">
        Username: <input type="text" name="username" required>
        <br>
        Password: <input type="password" name="password" required>
        <input type="submit" value="Login">
        </form>
        """
    )


@app.post("/login")
def login(response: Response, username: str = Form(...), password: str = Form(...)):
    if username not in users:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="Invalid user or password"
        )
    db_password = users[username]["password"]
    if not password == db_password:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="Invalid user or password"
        )
    token = jwt.encode({"sub": username}, secret_key)
    response.set_cookie("session", token)
    return {"ok": True}


@app.get("/private")
def read_private(username: str = Depends(get_current_user)):
    return {"username": username, "private": "get some private data"}
29reactions
Technerdercommented, Sep 13, 2020

It’s already in place. More or less like the rest of the security tools. And it’s compatible with the rest of the parts, integrated with OpenAPI (as possible), but probably most importantly, with dependencies.

It’s just not properly documented yet. 😞

But still, it works 🚀 e.g.

from fastapi import FastAPI, Form, HTTPException, Depends
from fastapi.security import APIKeyCookie
from starlette.responses import Response, HTMLResponse
from starlette import status
from jose import jwt


app = FastAPI()

cookie_sec = APIKeyCookie(name="session")

secret_key = "someactualsecret"

users = {"dmontagu": {"password": "secret1"}, "tiangolo": {"password": "secret2"}}


def get_current_user(session: str = Depends(cookie_sec)):
    try:
        payload = jwt.decode(session, secret_key)
        user = users[payload["sub"]]
        return user
    except Exception:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="Invalid authentication"
        )


@app.get("/login")
def login_page():
    return HTMLResponse(
        """
        <form action="/login" method="post">
        Username: <input type="text" name="username" required>
        <br>
        Password: <input type="password" name="password" required>
        <input type="submit" value="Login">
        </form>
        """
    )


@app.post("/login")
def login(response: Response, username: str = Form(...), password: str = Form(...)):
    if username not in users:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="Invalid user or password"
        )
    db_password = users[username]["password"]
    if not password == db_password:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="Invalid user or password"
        )
    token = jwt.encode({"sub": username}, secret_key)
    response.set_cookie("session", token)
    return {"ok": True}


@app.get("/private")
def read_private(username: str = Depends(get_current_user)):
    return {"username": username, "private": "get some private data"}

Is this a good/recommended way of implementing secure cookie based authentication?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Jeremy Howard on Twitter: "Both Starlette and fastapi are ...
Anyone spent significant time with both fastapi and starlette, ... [FEATURE] First-class session support in FastAPI · Issue #754 · tiangolo/fastapi.
Read more >
Features - FastAPI
Session and Cookie support. 100% test coverage. 100% type annotated codebase. Pydantic features¶. FastAPI is fully compatible with (and based on) ...
Read more >
FAST and MODERN Python API with FastAPI - YouTube
One of the front-runners in this next generation of Python web frameworks is FastAPI. You'll find first-class support for async/await, ...
Read more >
tiangolo/fastapi - Gitter
The current version of Saasify only supports generating SaaS APIs from TypeScript, but we're very interested in adding first-class Python support via ...
Read more >
Moving from Flask to FastAPI - TestDriven.io
Unlike Flask, FastAPI is an ASGI (Asynchronous Server Gateway Interface) ... Flask does not have true first-class support for it by default.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found