Session Fixation in Fastapi with Dynamodb
Session Fixation in Fastapi with Dynamodb — how this specific combination creates or exposes the vulnerability
Session fixation occurs when an application allows an attacker to force a user’s session identifier to a known value. In a Fastapi application that uses Amazon DynamoDB as the session store, this typically arises from how session identifiers are created, assigned, and validated. Fastapi does not enforce a new session identifier after authentication, and if the application stores session metadata in DynamoDB using a predictable or user-supplied identifier, an attacker can set a victim’s session token and later hijack the authenticated session.
DynamoDB’s role is storage and retrieval; it does not generate identifiers or enforce session semantics. If your Fastapi code saves session records keyed by a token that can be set by the client (for example via a header, cookie, or query parameter) and does not rotate that token after login, the vulnerability exists at the application layer but is realized through the DynamoDB access patterns. A malicious actor can craft a URL with a known session ID, get the victim to authenticate, and then retrieve the associated item from DynamoDB using the known key to impersonate the victim.
Consider a naive implementation where the session ID is taken directly from a cookie and used as the primary key in a DynamoDB table without regeneration on authentication:
import uuid
from fastapi import Fastapi, Request, Response, Depends, HTTPException
import boto3
app = Fastapi()
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
session_table = dynamodb.Table('app_sessions')
async def get_session(session_id: str):
resp = session_table.get_item(Key={'session_id': session_id})
return resp.get('Item')
@app.post('/login')
async def login(username: str, password: str, response: Response, request: Request):
# Assume validate_user returns a user dict
user = validate_user(username, password)
if not user:
raise HTTPException(status_code=401, detail='Invalid credentials')
# Vulnerable: session_id may be provided by the client
session_id = request.cookies.get('session_id') or str(uuid.uuid4())
session_table.put_item(Item={'session_id': session_id, 'user_id': user['id']})
response.set_cookie('session_id', session_id)
return {'ok': True}
In this pattern, if the client can supply session_id (via cookie or header), an attacker can fix the token before authentication. After the victim logs in, the attacker reads the DynamoDB item and uses the same token to gain access. DynamoDB simply stores what the application writes; the risk stems from trusting client-provided identifiers and failing to bind session creation to authentication events.
Additionally, because scans testing unauthenticated attack surfaces include checks for IDOR and authentication weaknesses, a poorly scoped DynamoDB access pattern can expose session items to horizontal privilege escalation if authorization checks are inconsistent. The scanner flags these scenarios under Authentication and BOLA/IDOR checks, emphasizing that secure session handling must include token regeneration and strict server-side validation regardless of the storage backend.
Dynamodb-Specific Remediation in Fastapi — concrete code fixes
To mitigate session fixation in a Fastapi application backed by DynamoDB, ensure that a new, server-generated session identifier is created immediately after successful authentication and that the old identifier (if any) is invalidated. Never rely on client-supplied values for the session key used in DynamoDB. Below is a secure pattern that generates a cryptographically random token, stores it in DynamoDB with a reasonable TTL, and binds the token to the authenticated user.
Use a server-side UUID for the session key and avoid exposing session management to the client:
import secrets
import time
from fastapi import Fastapi, Request, Response, HTTPException
import boto3
from datetime import datetime, timezone
app = Fastapi()
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
session_table = dynamodb.Table('app_sessions')
def generate_session_token():
# 32 bytes (256 bits) random token encoded as hex
return secrets.token_hex(32)
def current_unix_time():
return int(datetime.now(timezone.utc).timestamp())
async def store_session(session_id: str, user_id: str, ttl_seconds: int = 3600):
ttl = current_unix_time() + ttl_seconds
session_table.put_item(
Item={
'session_id': session_id,
'user_id': user_id,
'created_at': current_unix_time(),
'expires_at': ttl
}
)
async def invalidate_session(session_id: str):
session_table.delete_item(Key={'session_id': session_id})
async def get_session(session_id: str):
resp = session_table.get_item(Key={'session_id': session_id})
item = resp.get('Item')
if not item:
return None
if item.get('expires_at', 0) < current_unix_time():
await invalidate_session(session_id)
return None
return item
@app.post('/login')
async def login(username: str, password: str, response: Response):
user = validate_user(username, password)
if not user:
raise HTTPException(status_code=401, detail='Invalid credentials')
# Generate a new token on each successful login
session_id = generate_session_token()
await store_session(session_id, user['id'])
response.set_cookie('session_id', session_id, httponly=True, secure=True, samesite='lax')
return {'ok': True}
@app.post('/logout')
async def logout(request: Request, response: Response):
session_id = request.cookies.get('session_id')
if session_id:
await invalidate_session(session_id)
response.delete_cookie('session_id')
return {'ok': True}
The remediation uses DynamoDB for storage but ensures that the session identifier is generated server-side, cryptographically random, and rotated on login. It also stores an expiration timestamp and checks it on retrieval to handle stale items. For continuous protection across deployments, integrate these checks into your pipeline using the middleBrick CLI to scan from terminal with middlebrick scan <url>, or add the GitHub Action to your CI/CD to fail builds if security scores drop below your chosen threshold. Teams seeking ongoing visibility can use the Pro plan for continuous monitoring and alerts, while the MCP Server enables scanning APIs directly from AI coding assistants within your development environment.
Finally, validate that your authorization logic consistently checks ownership and scope for each DynamoDB item access, reducing the risk of IDOR even if session tokens are ever exposed.