Authentication, sessions, and CSRF¶
Django request¶
Inject a Django-compatible request when existing services expect
django.http.HttpRequest:
from typing import Annotated
from django.http import HttpRequest
from fastapi import Depends
from django_fastapi import get_django_request
DjangoRequest = Annotated[HttpRequest, Depends(get_django_request)]
The adapter constructs one request per FastAPI request and caches it. It sets
up Django's configured session backend and resolves request.user.
Built-in user dependencies¶
get_current_user: authenticated user orAnonymousUser.get_authenticated_user: authenticated user or HTTP 401.get_current_staff_user: staff user or HTTP 401/403.
The package cannot know a project's concrete custom user class. Projects may
wrap these dependencies and perform an isinstance check or cast locally.
Session authentication¶
The default resolver runs Django's normal session authentication. The FastAPI
and Django paths must receive the same session cookie. Separate services must
share the session backend, SECRET_KEY, cookie name, domain, path, security,
and SameSite settings.
Session changes made through the adapted request are saved back onto the FastAPI response:
@router.post("/preferences")
def update_preferences(request: DjangoRequest) -> dict[str, bool]:
request.session["compact_mode"] = True
return {"saved": True}
Custom authentication resolver¶
A resolver receives (fastapi_request, django_request), may be sync or async,
and returns a Django user, AnonymousUser, or None. The first authenticated
user wins. None and anonymous users allow the next resolver to run.
from django.contrib.auth import get_user_model
from fastapi import Request
async def bearer_resolver(request: Request, _django_request):
token = request.headers.get("authorization", "").removeprefix("Bearer ")
if not token:
return None
subject = await verify_token_and_get_subject(token)
return await get_user_model().objects.aget(external_id=subject)
The resolver owns signature, issuer, audience, expiry, revocation, and scope validation. The bridge deliberately does not implement token policy.
CSRF¶
CSRF is enabled by default for POST, PUT, PATCH, and DELETE. Django's
normal csrftoken cookie and X-CSRFToken header work.
Keep CSRF enabled whenever a browser can authenticate with a cookie. Disabling CSRF is appropriate only when an endpoint cannot use cookie authentication and has another complete request-authentication mechanism.
Exempt one operation:
from django_fastapi import csrf_exempt
@router.post("/provider-webhook")
@csrf_exempt
def webhook():
...
The endpoint must then validate the provider signature itself.
Custom HTTP session hooks¶
Set SESSION_MIDDLEWARE to a dotted class path when your session backend needs
native async I/O. Omit it to retain Django's standard SessionMiddleware.
The constructor receives get_response; synchronous process_request(request)
constructs the session store and must not perform blocking I/O. An optional
async aprocess_request(request) runs next, before authentication.
Before response headers are sent, the bridge calls
async aprocess_response(request, response) when present; otherwise it runs
synchronous process_response inside database_sync_to_async. The response hook
must return the response. Async hooks must be coroutine methods; invalid classes
fail at application creation with ImproperlyConfigured.
The response passed to the hook is a session-only scratch response. Only changed
Vary and generated cookies are merged into the outgoing response. Existing
Set-Cookie headers survive, and a bodyless 204/304 response does not acquire a
synthetic Content-Type. Set other response headers in application middleware.
Session modifications made after headers have been sent cannot be persisted.
Hook failures propagate as errors; a cache outage must not become an anonymous
session. Keep project authentication and authorization policy in your resolver.
WebSocket helpers use standard Django session authentication; they do not inherit
SESSION_MIDDLEWARE, its async hooks, or HTTP AUTH_RESOLVERS.