Session Fixation in Django with Bearer Tokens
Session Fixation in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Session fixation occurs when an attacker sets or guesses a user’s session identifier and then tricks the user into authenticating with that known value. In Django, the default session framework uses cookies (sessionid) and supports cookie-based authentication. When Bearer Tokens are introduced—commonly via the Authorization: Bearer <token> header for API or token-based flows—misconfiguration can tie authentication state to a predictable or static token without properly rotating or validating the session context.
Consider a scenario where a backend authenticates requests using a Bearer token but also relies on Django’s session cookie for additional authorization checks. If token issuance does not create a fresh session or bind the token securely to a server-side session, an attacker can fix a token (e.g., by persuading a victim to visit a crafted link with a known token in a header or query parameter) and later reuse it. Cross-application frameworks or middleware that copy headers into session-like stores without regeneration can inadvertently preserve the fixed token across authentication steps, violating the principle that session state must change after login.
Django’s session store is typically signed but not inherently designed for bearer-style token usage unless explicitly integrated. If you expose a token endpoint that issues Bearer tokens and also sets a session cookie without invalidating any pre-existing session, the token effectively becomes a fixation vector. Attack vectors include social engineering links such as https://example.com/login?next=/dashboard&token=attacker_controlled or header manipulation in single-page applications where the token is read from local storage and sent via Authorization headers without additional nonce or binding checks.
Another subtle risk arises when APIs accept Bearer tokens and rely on Django’s session middleware for CSRF or permission checks. If the token is static or long-lived and the session is not re-keyed after privilege changes, an attacker with a fixed token may retain access even after the legitimate user rotates credentials. This is especially relevant in OAuth2-like flows where access tokens are used in headers but the application mistakenly treats them as session identifiers without proper scope and revocation mechanisms.
Real-world patterns that can lead to fixation include: using query parameters to pass tokens (e.g., /api/resource?access_token=abc), failing to enforce one-time token binding, or not rotating session keys post-authentication. Even when using JWTs, if the application stores decoded claims in the session without verifying freshness on each request, the token’s static nature can be abused to maintain unauthorized access.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
Remediation focuses on ensuring that authentication state is never derived solely from a predictable or user-supplied token, and that session identifiers are regenerated upon privilege changes. Below are concrete patterns for Django when working with Bearer tokens.
1. Use Django’s session framework with token binding, not token-as-session
Do not treat the Bearer token as the session ID. Instead, use the token to authenticate the request and map it to a server-side session that you control. Enforce session rotation after login.
from django.contrib.auth import login, logout
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.models import User
import secrets
@csrf_exempt
def token_login(request):
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if not auth_header.startswith('Bearer '):
return JsonResponse({'error': 'Missing Bearer token'}, status=401)
token = auth_header.split(' ')[1]
# Validate token against your identity provider or database
user = validate_bearer_token(token) # Implement this securely
if user and user.is_active:
# Regenerate session key to prevent fixation
if request.session.session_key:
request.session.flush()
else:
request.session.create()
login(request, user)
# Optionally bind token metadata to session for auditing
request.session['token_jti'] = extract_jti(token)
return JsonResponse({'status': 'ok', 'session_id': request.session.session_key})
return JsonResponse({'error': 'Invalid token'}, status=401)
def validate_bearer_token(token: str):
# Example: validate against a model or external introspection endpoint
from .models import Token
try:
return Token.objects.get(key=token).user
except Token.DoesNotExist:
return None
2. Rotate session identifiers on privilege changes
After authentication or any privilege escalation, explicitly flush the session to issue a new identifier. This ensures that a token fixed before login cannot be reused after login.
from django.contrib.auth import login as auth_login
def login_and_rotate_session(request, user):
# Flush existing session to prevent fixation
request.session.flush()
request.session.create()
auth_login(request, user)
# Store additional security metadata
request.session.set_expiry(3600) # 1 hour
request.session['authenticated_at'] = str(timezone.now())
3. Secure token handling in API views
When using token-based authentication (e.g., via Django REST Framework), ensure tokens are not stored or logged in a way that enables fixation. Use short-lived tokens and refresh rotation, and bind tokens to a per-session context where applicable.
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import status
class ProtectedAPIView(APIView):
def get(self, request: Request):
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return Response({'detail': 'Unauthorized'}, status=status.HTTP_401_UNAUTHORIZED)
token = auth.split(' ')[1]
user = validate_bearer_token_against_db(token)
if not user or not user.is_active:
return Response({'detail': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)
# Ensure token usage aligns with session context; avoid echoing token in responses
return Response({'data': 'secure'})
4. Middleware and CSRF considerations
Ensure that CSRF checks remain intact and that Bearer tokens are not inadvertently exposed in logs or error messages. Avoid passing tokens in URLs or query parameters. Use HTTPS to protect tokens in transit.
5. Mapping to compliance and testing
These practices align with OWASP API Security Top 10 risks such as Broken Object Level Authorization (BOLA) and Security Misconfiguration. You can validate your fixes using middleBrick’s scans, which include checks for IDOR, Authentication, and Unsafe Consumption. middleBrick’s CLI makes it easy to integrate scans into local workflows: middlebrick scan https://your-api.example.com. For CI/CD, the GitHub Action can fail builds if risk scores drop below your chosen threshold, and the Dashboard lets you track security scores over time.