HIGH session fixationdjangobearer tokens

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.

Frequently Asked Questions

Can Bearer tokens be used safely with Django session cookies?
Yes, but do not treat the token as the session ID. Use the token to authenticate the request and map it to a server-side session, and always regenerate the session key after login to prevent fixation.
How can I detect session fixation risks in my API?
Use a scanner that tests authentication and IDOR/BOLA checks against both cookie and header-based auth. middleBrick runs parallel checks including Authentication and BOLA/IDOR and provides prioritized findings with remediation guidance.