FastAPI WebSockets with token auth and broadcast, tested with TestClient: working versions (tested 2026-10-15)
FastAPI WebSocket endpoint that authenticates via query token, rejects with a close code and broadcasts to connected clients; tested with TestClient.websocket_connect.
passed2 passed · 0 failed2026-10-15 11:53 UTC- Status
- passed
- Last passed
- 2026-10-15 11:53 UTC
- Tests
- 2 / 2
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38730348195
The code
Exactly the files that passed. Your agent can pull them with get_recipe("fastapi-websocket-auth") (install), or see them on GitHub.
fastapi==0.143.1 httpx==0.28.1 pytest==9.1.1
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, status
app = FastAPI()
TOKENS = {"t-ada": "ada", "t-bob": "bob"}
class Hub:
def __init__(self):
self.clients: dict[WebSocket, str] = {}
async def broadcast(self, data: dict):
for ws in list(self.clients):
await ws.send_json(data)
hub = Hub()
@app.websocket("/ws")
async def ws_endpoint(websocket: WebSocket, token: str = ""):
user = TOKENS.get(token)
if not user:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
await websocket.accept()
hub.clients[websocket] = user
try:
while True:
msg = await websocket.receive_json()
await hub.broadcast({"from": user, "text": msg.get("text", "")})
except WebSocketDisconnect:
hub.clients.pop(websocket, None)
import pytest
from fastapi.testclient import TestClient
from starlette.websockets import WebSocketDisconnect
from app.main import app
client = TestClient(app)
def test_echo_broadcast_to_all():
with client.websocket_connect("/ws?token=t-ada") as a, client.websocket_connect("/ws?token=t-bob") as b:
a.send_json({"text": "hi"})
assert a.receive_json() == {"from": "ada", "text": "hi"}
assert b.receive_json() == {"from": "ada", "text": "hi"}
def test_bad_token_closed_with_1008():
with pytest.raises(WebSocketDisconnect) as e:
with client.websocket_connect("/ws?token=nope") as ws:
ws.receive_json()
assert e.value.code == 1008
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
FastAPI + Pydantic v2 validators, discriminated unions and serializers
Pydantic v2 patterns in FastAPI that agents often get wrong: field_validator, model_validator, discriminated unions, computed_field and response_model filtering.
FastAPI + PyJWT bearer auth
OAuth2 password flow in FastAPI issuing PyJWT tokens, with a current-user dependency guarding routes.
FastAPI + SQLAlchemy 2 CRUD with dependency-injected sessions
FastAPI routes backed by SQLAlchemy 2.x typed models, a per-request session dependency, and TestClient tests on SQLite.
FastAPI + Stripe webhook signature verification
Verify Stripe webhook signatures in FastAPI from the raw request body and dispatch on event type. Tested offline with locally signed payloads.