Programming

FastAPI WebSocket Cookie Dependency Injection: Why It Fails and How to Fix It

Learn why FastAPI's dependency injection can't access cookies in WebSocket endpoints and discover multiple solutions for proper cookie handling in FastAPI WebSockets.

1 answer 1 view

In a normal HTTP route FastAPI can inject a Request (or a Cookie dependency) and the cookie value is automatically passed to the dependency function.
With a WebSocket endpoint that is not the case – the dependency system is only wired to the WebSocket object, not to a Request.

So when you write something like

python
async def ws_endpoint(websocket: WebSocket, cookie: str = Depends(get_cookie)):
 ...

FastAPI will try to call get_cookie() with the parameters it knows about.
If get_cookie is defined as

python
def get_cookie(cookie: str = Cookie(...)):
 return cookie

FastAPI cannot satisfy the Cookie parameter because there is no Request in the WebSocket context, and the dependency returns None.
The cookie is still present in websocket.cookies, but it never reaches the dependency.

How to fix it

  1. Read the cookie directly from the WebSocket
python
async def ws_endpoint(websocket: WebSocket):
await websocket.accept()
cookie_value = websocket.cookies.get("my_cookie")
...
  1. Make the dependency accept a WebSocket instead of a Cookie
python
def get_cookie(websocket: WebSocket) -> str | None:
return websocket.cookies.get("my_cookie")

async def ws_endpoint(websocket: WebSocket, cookie: str | None = Depends(get_cookie)):
...
  1. Use a custom dependency that parses the Cookie header manually
python
def get_cookie_from_header(websocket: WebSocket) -> str | None:
cookie_header = websocket.headers.get("cookie")
if not cookie_header:
return None
# simple parsing – you can use `http.cookies.SimpleCookie` if you need more robust parsing
for part in cookie_header.split(";"):
name, _, value = part.strip().partition("=")
if name == "my_cookie":
return value
return None

Bottom line

The cookie is not passed to the dependency because FastAPI’s dependency injection for WebSockets does not support Cookie (or Request) parameters.
Either read the cookie from websocket.cookies yourself, or write a dependency that takes a WebSocket and extracts the cookie from it.

FastAPI WebSocket cookie handling presents a unique challenge where dependency injection can’t access cookies automatically like in HTTP routes. When using Depends(get_cookie) with a Cookie parameter in WebSocket endpoints, the dependency returns None even though the cookie exists in websocket.cookies. This limitation occurs because FastAPI’s WebSocket dependency injection system doesn’t create a Request object, which is required for automatic cookie injection.


Contents


Understanding the FastAPI WebSocket Cookie Challenge

In standard FastAPI HTTP routes, cookie handling is straightforward thanks to the framework’s dependency injection system. You can define a dependency with Cookie() parameters, and FastAPI automatically extracts and passes the cookie value:

python
from fastapi import Cookie, Depends

async def get_user_cookie(session_id: str = Cookie(...)):
 # FastAPI automatically extracts session_id from cookies
 return session_id

@app.get("/profile")
async def get_profile(session_id: str = Depends(get_user_cookie)):
 # Works perfectly in HTTP routes
 user = get_user_by_session(session_id)
 return {"user": user}

However, this elegant approach breaks down when working with WebSocket endpoints. The fundamental issue lies in how FastAPI structures its WebSocket dependency injection. When you define a WebSocket route with dependencies like:

python
async def ws_endpoint(websocket: WebSocket, session_id: str = Depends(get_user_cookie)):
 # This won't work!

FastAPI attempts to call get_user_cookie() with the parameters it knows about - in this case, only the websocket object. Since get_user_cookie() expects a Cookie parameter that requires a Request context, the dependency cannot be satisfied and returns None.

The cookie is actually available in websocket.cookies as a dictionary, but the dependency system has no way to access it through the standard Cookie() injection mechanism. This creates a disconnect between what developers expect based on their HTTP experience and how WebSocket dependency injection actually works in FastAPI.

According to the official FastAPI WebSocket documentation, this limitation exists because WebSocket connections don’t go through the same request-response cycle as HTTP requests. The cookie information is present during the initial handshake but isn’t packaged into a Request object that dependencies can access.


Why Standard Cookie Dependencies Fail in WebSockets

The core reason standard cookie dependencies fail in WebSocket endpoints comes down to architectural differences between HTTP and WebSocket connections in FastAPI. When a WebSocket connection is established, FastAPI follows these steps:

  1. Perform the WebSocket handshake (HTTP request)
  2. Upgrade the connection to WebSocket protocol
  3. Create and provide the WebSocket object to your endpoint

The critical insight is that step 1 creates a temporary Request object to handle the handshake, but this object is not passed to your dependency functions. Instead, only the final WebSocket object is available.

In HTTP routes, FastAPI creates a Request object and makes it available to the dependency injection system. This allows dependencies to access request-related data including cookies, headers, query parameters, and path parameters through specialized injection mechanisms like Cookie(), Header(), Query(), etc.

When you define a dependency using Cookie():

python
def get_cookie(cookie: str = Cookie(...)):
 return cookie

FastAPI internally looks for the cookie in the Request object’s cookies. Since no Request object is created and passed to WebSocket dependencies, the Cookie() parameter cannot be resolved, resulting in None.

The FastAPI GitHub discussions confirm this is an intentional design choice. WebSocket connections are meant to be lightweight and stateful, carrying forward the initial handshake information rather than recreating a full request context for each message.

This architectural decision has practical implications:

  • Less memory overhead (no full Request object for each WebSocket)
  • Simpler dependency system for WebSocket-specific logic
  • Clear separation between HTTP and WebSocket concerns
  • Performance optimization for long-lived connections

However, it creates the challenge of accessing cookie information that developers have come to expect through dependency injection in HTTP routes.


Solutions for Accessing Cookies in FastAPI WebSockets

Solution 1: Direct Cookie Access

The simplest approach is to bypass dependency injection entirely and access cookies directly from the WebSocket object:

python
from fastapi import WebSocket

async def ws_endpoint(websocket: WebSocket):
 await websocket.accept()
 
 # Direct cookie access
 session_id = websocket.cookies.get("session_id")
 
 if not session_id:
 await websocket.close(code=4003, reason="Authentication required")
 return
 
 # Proceed with authenticated session
 user = get_user_by_session(session_id)
 await websocket.send_text(f"Welcome, {user.name}!")

This approach is straightforward and works perfectly for simple use cases. The websocket.cookies attribute contains a dictionary of all cookies from the initial handshake.

Advantages:

  • Simple to implement
  • No additional dependency complexity
  • Immediate access to all cookies
  • Clear and direct code flow

Disadvantages:

  • Repeats cookie validation logic across endpoints
  • Doesn’t leverage dependency injection benefits
  • Can lead to code duplication

Solution 2: WebSocket-Aware Dependencies

For better code organization, create dependencies that accept a WebSocket parameter instead of relying on automatic cookie injection:

python
from fastapi import WebSocket, Depends

def get_websocket_cookie(websocket: WebSocket, cookie_name: str) -> str | None:
 """Dependency that extracts a specific cookie from WebSocket"""
 return websocket.cookies.get(cookie_name)

async def ws_endpoint(
 websocket: WebSocket,
 session_id: str = Depends(lambda ws: get_websocket_cookie(ws, "session_id"))
):
 await websocket.accept()
 
 if not session_id:
 await websocket.close(code=4003, reason="Authentication required")
 return
 
 # Business logic here
 user = get_user_by_session(session_id)
 await websocket.send_text(f"Authenticated as {user.name}")

This approach maintains dependency injection patterns while adapting to WebSocket constraints. You can also create a more reusable version:

python
def create_cookie_dependency(cookie_name: str):
 def dependency(websocket: WebSocket) -> str | None:
 return websocket.cookies.get(cookie_name)
 return dependency

# Usage
async def ws_endpoint(
 websocket: WebSocket,
 session_id: str = Depends(create_cookie_dependency("session_id"))
):
 # Implementation

Advantages:

  • Reuses dependency injection patterns
  • Centralizes cookie extraction logic
  • More testable than direct access
  • Consistent with FastAPI design patterns

Disadvantages:

  • Requires custom dependency creation
  • Slightly more boilerplate code
  • Still limited to cookie extraction (no other request features)

Solution 3: Manual Header Parsing

For cases where you need more control or want to handle edge cases manually, parse the Cookie header directly:

python
from fastapi import WebSocket

def parse_cookie_header(websocket: WebSocket, cookie_name: str) -> str | None:
 """Manually parse Cookie header from WebSocket"""
 cookie_header = websocket.headers.get("cookie")
 if not cookie_header:
 return None
 
 # Simple parsing - can be enhanced with http.cookies.SimpleCookie
 for part in cookie_header.split(";"):
 name, _, value = part.strip().partition("=")
 if name == cookie_name:
 return value
 return None

async def ws_endpoint(websocket: WebSocket):
 await websocket.accept()
 
 session_id = parse_cookie_header(websocket, "session_id")
 
 if not session_id:
 await websocket.close(code=4003, reason="Authentication required")
 return
 
 # Continue with authenticated session
 user = get_user_by_session(session_id)
 await websocket.send_json({"status": "connected", "user": user.name})

This approach gives you full control over cookie parsing, including handling complex cookie formats, encoding issues, or special requirements.

Advantages:

  • Complete control over parsing logic
  • Can handle edge cases
  • Works with any cookie format
  • No dependency on internal FastAPI implementation

Disadvantages:

  • More complex implementation
  • Reinventing functionality that already exists
  • Potential for bugs in custom parsing

Solution 4: Hybrid Authentication Approach

For applications that need to handle both HTTP and WebSocket authentication consistently, consider a hybrid approach:

python
from fastapi import WebSocket, Cookie, Depends, Request, HTTPException

async def get_auth_token(request: Request = None, websocket: WebSocket = None):
 """Unified authentication that works with both HTTP and WebSocket"""
 if websocket:
 # WebSocket path
 token = websocket.cookies.get("auth_token")
 elif request:
 # HTTP path
 token = request.cookies.get("auth_token")
 else:
 raise ValueError("Either request or websocket must be provided")
 
 if not token:
 raise HTTPException(status_code=401, detail="Authentication required")
 
 return token

# HTTP endpoint
@app.get("/api/data")
async def get_data(token: str = Depends(get_auth_token)):
 return {"message": "HTTP data"}

# WebSocket endpoint
async def ws_endpoint(websocket: WebSocket, token: str = Depends(get_auth_token)):
 await websocket.accept()
 # WebSocket logic here

This pattern provides a unified authentication layer that works across both HTTP and WebSocket endpoints.

Advantages:

  • Consistent authentication across endpoint types
  • Centralized authentication logic
  • Reduces code duplication
  • Easy to extend for other authentication methods

Disadvantages:

  • More complex dependency design
  • Requires conditional logic
  • May introduce coupling between HTTP and WebSocket concerns

Best Practices for Cookie Authentication in WebSockets

1. Prefer WebSocket-Aware Dependencies

While direct cookie access is simple, creating WebSocket-aware dependencies provides better organization and reusability:

python
# In your dependencies module
from fastapi import WebSocket

def get_session_cookie(websocket: WebSocket) -> str:
 """Extract session cookie from WebSocket with validation"""
 session_id = websocket.cookies.get("session_id")
 if not session_id:
 raise ValueError("Session cookie missing")
 
 # Additional validation if needed
 if len(session_id) < 32:
 raise ValueError("Invalid session format")
 
 return session_id

# In your WebSocket module
from fastapi import WebSocket, Depends

async def ws_endpoint(websocket: WebSocket, session_id: str = Depends(get_session_cookie)):
 await websocket.accept()
 # Your business logic

2. Implement Proper Error Handling

Always validate cookies and handle authentication failures gracefully:

python
async def ws_endpoint(websocket: WebSocket):
 try:
 await websocket.accept()
 
 session_id = websocket.cookies.get("session_id")
 if not session_id:
 await websocket.close(code=4003, reason="Authentication required")
 return
 
 user = authenticate_user(session_id)
 if not user:
 await websocket.close(code=4004, reason="Invalid session")
 return
 
 # Proceed with authenticated user
 await websocket.send_json({"status": "authenticated", "user": user.name})
 
 except Exception as e:
 await websocket.close(code=4005, reason="Server error")
 # Log the error

3. Use Secure Cookie Settings

When setting cookies for WebSocket authentication, always use appropriate security settings:

python
from fastapi import Response

@app.post("/login")
async def login(response: Response, credentials: UserCredentials):
 user = authenticate_user(credentials.username, credentials.password)
 if not user:
 raise HTTPException(status_code=401, detail="Invalid credentials")
 
 # Set secure cookie
 response.set_cookie(
 key="session_id",
 value=user.session_id,
 httponly=True, # Prevent JavaScript access
 secure=True, # Only send over HTTPS
 samesite="lax" # Balance security and usability
 )
 
 return {"status": "success"}

4. Consider Token-Based Authentication

For WebSocket applications, consider using JWT tokens or other bearer tokens instead of session cookies:

python
async def ws_endpoint(websocket: WebSocket):
 # Extract token from authorization header
 auth_header = websocket.headers.get("authorization")
 if not auth_header or not auth_header.startswith("Bearer "):
 await websocket.close(code=4003, reason="Authentication required")
 return
 
 token = auth_header[7:] # Remove "Bearer " prefix
 
 try:
 payload = verify_jwt_token(token)
 user = get_user_by_id(payload["user_id"])
 
 await websocket.accept()
 # Continue with authenticated user
 
 except JWTError:
 await websocket.close(code=4004, reason="Invalid token")

5. Implement Connection Cleanup

Always properly close WebSocket connections when authentication fails:

python
async def ws_endpoint(websocket: WebSocket):
 try:
 await websocket.accept()
 
 # Authentication
 session_id = websocket.cookies.get("session_id")
 if not session_id:
 await websocket.close(code=4003, reason="Authentication required")
 return
 
 user = authenticate_user(session_id)
 if not user:
 await websocket.close(code=4004, reason="Invalid session")
 return
 
 # Business logic
 await websocket.send_json({"status": "connected"})
 
 except WebSocketDisconnect:
 # Handle normal disconnection
 pass
 except Exception as e:
 await websocket.close(code=5000, reason=f"Server error: {str(e)}")

6. Rate Limit Authentication Attempts

Protect against brute force attacks by implementing rate limiting:

python
from fastapi import Request, WebSocket
from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

async def ws_endpoint(websocket: WebSocket):
 # Rate limit authentication attempts
 try:
 await limiter.limit("5/minute")(websocket)
 except Exception:
 await websocket.close(code=429, reason="Too many authentication attempts")
 return
 
 # Authentication logic
 session_id = websocket.cookies.get("session_id")
 # ... rest of authentication

Troubleshooting Common Cookie Issues

1. Cookies Not Appearing in websocket.cookies

If cookies aren’t showing up in websocket.cookies, check these common issues:

Domain Consistency:
According to StackOverflow discussions, browsers often treat localhost and 127.0.0.1 as different domains. Ensure your cookie domain settings match the WebSocket connection URL:

python
# Set cookie with specific domain
response.set_cookie(
 key="session_id",
 value="abc123",
 domain="localhost" # Must match WebSocket connection URL
)

# When connecting to WebSocket:
# ws://localhost:8000/ws (✓ works)
# ws://127.0.0.1:8000/ws (✗ won't see cookie unless domain set to 127.0.0.1)

Cookie Path:
Make sure the cookie path matches your WebSocket endpoint path:

python
# For WebSocket endpoint at /ws
response.set_cookie(
 key="session_id",
 value="abc123",
 path="/ws" # Must match WebSocket endpoint path
)

Secure and HttpOnly Flags:
If your cookie uses Secure=True, it won’t be sent over non-HTTPS connections. For development, consider:

python
response.set_cookie(
 key="session_id",
 value="abc123",
 secure=False, # Set to False for HTTP in development
 httponly=True # Still recommended for security
)

2. CORS Configuration Issues

Cross-origin requests can interfere with cookie handling. Ensure your CORS configuration allows cookies:

python
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
 CORSMiddleware,
 allow_origins=["http://localhost:3000"], # Your frontend URL
 allow_credentials=True, # Essential for cookies!
 allow_methods=["*"],
 allow_headers=["*"],
)

When making the WebSocket connection from your frontend, ensure credentials are included:

javascript
// Frontend WebSocket connection
const ws = new WebSocket("ws://localhost:8000/ws", [], {
 withCredentials: true // Required for cookies
});

3. Cookie Expiration and Refresh

WebSocket connections can be long-lived, so consider cookie expiration:

python
from datetime import datetime, timedelta

@app.post("/login")
async def login(response: Response):
 # Create session
 session_id = generate_session_id()
 expires = datetime.utcnow() + timedelta(hours=24) # 24-hour expiration
 
 response.set_cookie(
 key="session_id",
 value=session_id,
 expires=expires,
 httponly=True,
 secure=True,
 )
 
 return {"session_id": session_id, "expires": expires}

For long-lived WebSocket connections, implement periodic token refresh:

python
async def ws_endpoint(websocket: WebSocket):
 last_refresh = time.time()
 
 while True:
 try:
 # Check if we need to refresh the token
 if time.time() - last_refresh > 3600: # Refresh every hour
 session_id = websocket.cookies.get("session_id")
 if session_id:
 # Refresh session
 new_session_id = refresh_session(session_id)
 # Note: Can't modify cookies in WebSocket, client must reconnect
 last_refresh = time.time()
 
 # Process WebSocket messages
 data = await websocket.receive_text()
 # Handle message
 
 except WebSocketDisconnect:
 break

4. Debugging Cookie Access

For debugging cookie issues, implement logging:

python
import logging
logging.basicConfig(level=logging.INFO)

async def ws_endpoint(websocket: WebSocket):
 logging.info(f"WebSocket connected from {websocket.client.host}")
 logging.info(f"Available cookies: {websocket.cookies}")
 
 # Continue with authentication...

Or implement a debug endpoint to inspect cookies:

python
@app.get("/debug/cookies")
async def debug_cookies(request: Request):
 return {
 "client_cookies": request.cookies,
 "headers": dict(request.headers)
 }

5. Handling Multiple Cookie Values

Some cookies may have multiple values (rare but possible):

python
def get_cookie_values(websocket: WebSocket, cookie_name: str) -> list[str]:
 """Extract all values for a cookie that may have multiple values"""
 cookie_header = websocket.headers.get("cookie")
 if not cookie_header:
 return []
 
 values = []
 for part in cookie_header.split(";"):
 name, _, value = part.strip().partition("=")
 if name == cookie_name:
 values.append(value)
 
 return values

# Usage
cookie_values = get_cookie_values(websocket, "session_id")
if cookie_values:
 # Use the appropriate value

6. Large Cookie Handling

Be aware of browser and server limitations on cookie size:

python
# Check cookie size before setting
def is_cookie_too_large(value: str, max_size: int = 4096) -> bool:
 """Check if cookie exceeds size limits"""
 return len(value) > max_size

if not is_cookie_too_large(large_session_data):
 response.set_cookie("large_data", large_session_data)
else:
 # Alternative: store large data in session and reference it
 response.set_cookie("data_ref", "ref123")
 store_large_data("ref123", large_session_data)

Sources

  1. FastAPI WebSocket Documentation - Official guide on WebSocket handling and dependency injection limitations: https://fastapi.tiangolo.com/advanced/websockets/
  2. FastAPI GitHub Discussions - Community discussion on cookie handling in WebSockets with implementation examples: https://github.com/fastapi/fastapi/discussions/10658
  3. StackOverflow Cookie Handling - Troubleshooting domain-specific cookie issues in WebSocket connections: https://stackoverflow.com/questions/72563254/can-not-get-cookie-from-localhost-websocket-fast-api
  4. FastAPI Response Cookies - Official documentation on setting cookies in FastAPI responses: https://fastapi.tiangolo.com/advanced/response-cookies/
  5. FastAPI Cookie Parameters - Tutorial on using Cookie dependencies in standard HTTP requests: https://fastapi.tiangolo.com/tutorial/cookie-params/

Conclusion

FastAPI WebSocket cookie handling requires a different approach than standard HTTP routes due to the absence of a Request object in the dependency injection system. The core issue is that Cookie() dependencies rely on HTTP request context that simply doesn’t exist in WebSocket connections.

By understanding the architectural differences between HTTP and WebSocket connections in FastAPI, developers can implement effective solutions. The three primary approaches—direct cookie access from websocket.cookies, WebSocket-aware dependencies, or manual header parsing—each offer different trade-offs between simplicity, reusability, and control.

For most applications, creating WebSocket-specific dependencies provides the best balance of organization and functionality. These dependencies can maintain the familiar dependency injection pattern while adapting to the WebSocket context. Additionally, following best practices around secure cookie settings, proper error handling, and connection cleanup ensures robust WebSocket authentication.

When troubleshooting cookie issues, common problems like domain mismatches, CORS configuration, and cookie expiration can usually be resolved by carefully reviewing connection details and cookie settings. By implementing the appropriate solution based on your specific requirements, you can create secure and efficient FastAPI WebSocket applications with proper cookie authentication.

Authors
Verified by moderation
Moderation
FastAPI WebSocket Cookie Dependency Injection: Why It Fails and How to Fix It