Token Leakage in Django with Basic Auth
Token Leakage in Django with Basic Auth — how this specific combination creates or exposes the vulnerability
Token leakage in Django when using HTTP Basic Authentication occurs when authentication tokens, session identifiers, or credentials are unintentionally exposed in logs, error messages, or through misconfigured responses. Basic Auth encodes a username:password pair in Base64 with no encryption; if used without HTTPS, the encoded token is trivial to decode and can be captured in transit. Even when HTTPS is enforced, developers sometimes embed tokens as query parameters or in custom headers that get logged by Django, web servers, or monitoring tools.
Django’s built-in authentication views and permission classes can inadvertently contribute to leakage when tokens are included in URLs or when error handling exposes stack traces containing credentials. For example, a view that accepts an Authorization header but fails to validate or sanitize input may return detailed exceptions that include the raw header value. These exceptions can be captured and indexed by search engines or exposed through insecure logging practices.
Another vector involves improper caching. If responses containing authorization tokens are cached by intermediate proxies or browsers without appropriate cache-control headers, tokens can persist beyond the intended session lifetime. MiddleBrick’s checks for Data Exposure and Unsafe Consumption highlight scenarios where authenticated responses are served over insecure channels or stored in locations accessible to unauthorized parties.
The interplay of Basic Auth’s simplicity and token handling in Django increases risk when developers assume transport-layer security alone is sufficient. Without additional safeguards such as short-lived tokens, strict referrer policies, and secure header handling, tokens can leak through logs, error reports, or client-side storage. Continuous scanning with tools that test unauthenticated attack surfaces helps surface these exposure paths before they are exploited.
Basic Auth-Specific Remediation in Django — concrete code fixes
To mitigate token leakage when using Basic Auth in Django, implement strict transport security, avoid embedding tokens in URLs, and ensure errors do not expose sensitive data. Below are concrete, working examples that demonstrate secure patterns.
1. Enforce HTTPS and Secure Headers
Ensure all traffic uses HTTPS and set security headers to prevent caching of sensitive responses.
from django.middleware.security import SecurityMiddleware
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# settings.py additions
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
2. Use Django REST Framework with Token Authentication Over HTTPS
Instead of relying on raw Basic Auth, use token-based authentication with DRF. This reduces the exposure of credentials in logs and URLs.
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
class SecureEndpoint(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
return Response({'status': 'authenticated', 'user': request.user.username})
3. Custom Basic Auth View with Safe Error Handling
If you must use Basic Auth, wrap authentication in a view that avoids exposing tokens in exceptions and explicitly validates credentials without leaking details.
import base64
from django.http import JsonResponse, HttpResponseBadRequest
from django.views import View
class SafeBasicAuthView(View):
def post(self, request):
auth = request.headers.get('Authorization', '')
if not auth.startswith('Basic '):
return JsonResponse({'error': 'Unauthorized'}, status=401)
try:
encoded = auth.split(' ')[1]
decoded = base64.b64decode(encoded).decode('utf-8')
username, password = decoded.split(':', 1)
# Perform validation without logging raw credentials
if username == 'admin' and password == 'secure_password':
return JsonResponse({'token': 'safe_token_abc123'})
else:
return JsonResponse({'error': 'Invalid credentials'}, status=401)
except Exception:
# Avoid exposing stack traces that may include auth data
return JsonResponse({'error': 'Bad request'}, status=400)
4. Prevent Token Leakage in Logs
Ensure that your logging configuration filters out Authorization headers. This prevents tokens from being written to log files.
import logging
class SensitiveDataFilter(logging.Filter):
def filter(self, record):
if hasattr(record, 'msg'):
record.msg = str(record.msg).replace('Authorization: Basic ', 'Authorization: [FILTERED] ')
return True
logger = logging.getLogger()
logger.addFilter(SensitiveDataFilter())