WebSockets¶
WebSocket routers are deliberately separate from HTTP routers. Django's CSRF middleware protects unsafe HTTP requests, while a WebSocket handshake is protected by strict Host and Origin validation.
Configure and mount one WebSocket endpoint¶
# project/realtime.py
from fastapi import APIRouter, WebSocket
from django_fastapi import resolve_websocket_auth, validate_websocket_origin
router = APIRouter()
@router.websocket("/")
async def realtime(websocket: WebSocket):
validate_websocket_origin(websocket)
auth = await resolve_websocket_auth(websocket)
await websocket.accept()
await websocket.send_json({"authenticated": auth.user.is_authenticated})
# settings.py
DJANGO_FASTAPI = {
"PREFIX": "/api",
"ROUTERS": ["project.api.router"],
"WEBSOCKET_ROUTERS": ["project.realtime.router"],
}
# asgi.py
from django.core.asgi import get_asgi_application
from django_fastapi import (
create_fastapi_app,
create_websocket_app,
mount_django_fastapi_app,
)
application = mount_django_fastapi_app(
get_asgi_application(),
create_fastapi_app(),
websocket_app=create_websocket_app(),
websocket_path="/ws",
)
Only the exact /ws path reaches the WebSocket app. Other WebSocket paths are
closed with code 1008 instead of falling through to Django.
Authentication and anonymous sessions¶
resolve_websocket_auth() loads the configured Django session cookie and
returns WebSocketAuth, containing the Django request, session, session key,
and current user. A missing session cookie is valid and produces an anonymous
user with no session key. Use require_websocket_user() when anonymous
connections are not allowed.
If an anonymous workflow needs a stable session audience, create and persist the Django session in the preceding HTTP response. A WebSocket handshake cannot reliably create a browser cookie after it has upgraded.
Origin security¶
Call validate_websocket_origin() before accept(). It validates Host
against Django's ALLOWED_HOSTS and requires Origin to match the effective
HTTP/HTTPS scheme, hostname, and port corresponding to the WS/WSS connection;
missing or untrusted values close with code 1008. This prevents another website
from using a victim's ambient session cookie to open your socket.
For an intentional cross-origin frontend, list complete origins explicitly in
DJANGO_FASTAPI["WEBSOCKET_TRUSTED_ORIGINS"], for example
["https://app.example.com"]. Merely listing two sibling domains in
ALLOWED_HOSTS does not permit them to open each other's WebSockets.
CSRF tokens do not protect WebSocket messages. Keep HTTP CSRF enabled for cookie-authenticated HTTP routes and use Origin validation for WebSockets.
Proxy headers and heartbeat¶
Forward the original Host, scheme, and client address only through trusted
proxies. Configure Uvicorn's --forwarded-allow-ips to the actual proxy
boundary. Forward Upgrade and Connection only on the intended WebSocket
location and disable proxy buffering there.
Protocol ping/pong is server configuration, not application routing. An application-level ping is still useful for detecting a half-open browser path and should carry an unpredictable nonce that the server echoes.
Root lifespan¶
Pass an async context manager through lifespan= when the root application
owns shared resources. The composer enters the supplied root lifespan and the
HTTP FastAPI lifespan once each. If a later startup step fails, already-entered
contexts are exited in reverse order. Mappings yielded by both contexts are
merged into request.state; values from the supplied root lifespan take
precedence when both mappings contain the same key.
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app):
await shared_hub.start()
try:
yield {"shared_hub": shared_hub}
finally:
await shared_hub.close()
The WebSocket application's own lifespan is not entered separately. Use
database_sync_to_async for each short ORM operation and finish transactions
before waiting for messages. HTTP AUTH_RESOLVERS and SESSION_MIDDLEWARE hooks
do not change resolve_websocket_auth(), which uses Django's standard session
middleware and authentication. Applications with additional account policy must
check it explicitly before accepting the connection.