Multiple transports¶
A web app and an API often live in the same backend. The browser wants a cookie session; the API wants a bearer token. With CRUDAuth you enable both transports and your routes don't change, because every transport resolves to the same Principal.
from crudauth import CRUDAuth, SessionTransport, BearerTransport
auth = CRUDAuth(
session=get_session, user_model=User, SECRET_KEY="change-me",
transports=[
SessionTransport(), # browsers: cookies + CSRF
BearerTransport(), # API / mobile / CLI: JWT
],
)
First credential wins¶
Transports are tried in the order you list them, and the first credential that's present
wins. A transport returns nothing when its credential is absent (so the next one is tried),
but it raises for a credential that's present but invalid (for example a session cookie that
fails its CSRF check). That hard failure propagates even under optional=True, because a
tampered credential is an attack signal, not "anonymous".
Order by precedence. If a request could carry both a cookie and a token, list the one you want to win first.
Narrowing a route to one transport¶
Some endpoints should only accept one kind of credential. Pass transport= to restrict it:
@app.post("/api/jobs")
async def create_job(user: Principal = Depends(auth.current_user(transport="bearer"))):
... # rejects a cookie session even if one is present
@app.get("/account")
async def account(user: Principal = Depends(auth.current_user(transport="session"))):
... # browser-only
You can also pass a list, transport=["session", "bearer"], to accept a subset.
CSRF stays where sessions are¶
CSRF is a property of the session transport. It's enforced on cookie-authenticated mutations and is irrelevant to bearer requests, which don't ride a cookie. So your bearer API paths never deal with CSRF, while your browser paths are protected automatically. Nothing in your route code has to know the difference.