Clickjacking in Django with Bearer Tokens
Clickjacking in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI redress attack where an invisible or disguised frame tricks a user into interacting with a page they did not intend to interact with. In Django, APIs that rely on Bearer Tokens for authentication can still be exposed to clickjacking when token handling is combined with pages that perform sensitive actions via GET requests or render forms without proper anti-CSRF protections.
Consider an API endpoint that accepts a Bearer Token in the Authorization header and also provides a page that includes JavaScript to automatically make authenticated requests. If that page embeds third-party content or is itself embedded in an attacker-controlled frame, an attacker can overlay UI elements to capture user input or trigger unintended API calls. Even when Bearer Tokens are used, browsers send credentials (including Authorization headers) with same-origin requests, so an embedded malicious form or script can cause the browser to perform actions on behalf of the authenticated user.
In a typical Django setup using token-based authentication, views might parse tokens from headers but neglect to enforce frame-busting or Content Security Policy (CSP) frame-ancestors rules. Without explicit X-Frame-Options or CSP frame-ancestors directives, the API’s web interface or any page that uses JavaScript to call the Bearer-token-protected endpoints can be loaded inside an iframe. An attacker can position invisible submit buttons or links over these iframes to induce clicks on privileged operations such as changing settings or initiating transactions.
Moreover, if Django templates render forms that rely on Bearer Tokens stored in JavaScript variables or in cookies without the SameSite and Secure attributes, the risk increases. Browsers will include cookies automatically, and if a CSRF token is missing or not validated for state-changing requests, clickjacking can lead to unauthorized actions. Even when using Bearer Tokens in headers, developers sometimes implement endpoints that accept unsafe methods like GET for convenience, which can be triggered via an embedded image or script from an attacker site, leading to unintended data exposure or changes.
To understand the impact, imagine a Django view that reads a token from the Authorization header and performs a money transfer based on query parameters without verifying the request origin. If an attacker crafts a page with an invisible form that submits to this view, and the view does not validate Referer or enforce CSRF protection for token-based requests, the victim’s browser will send the token along with the request, completing the transfer without user consent. This demonstrates how Bearer Tokens alone do not prevent clickjacking; complementary defenses are required.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
Remediation focuses on preventing embedding, enforcing CSRF where applicable, and ensuring tokens are handled securely in both browser and non-browser contexts. Below are concrete code examples for Django views and settings.
1. Set X-Frame-Options and Content Security Policy
Ensure responses include headers that prevent framing. In Django settings, add security middleware or use built-in configurations.
class XFrameOptionsMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response['X-Frame-Options'] = 'DENY'
return response
# In settings.py, ensure CSP frame-ancestors is set via a middleware or security library
# Example using django-csp (install via pip install django-csp):
# CSP_DEFAULT_SRC = ("'self'",)
# CSP_FRAME_ANCESTORS = ("'none'",)
2. Use SameSite and Secure Cookie Attributes for Session Tokens
If your Bearer Tokens are stored in cookies (e.g., for web-based clients), configure Django to protect them.
# settings.py
SESSION_COOKIE_SAMESITE = 'Strict'
SESSION_COOKIE_SECURE = True # Use HTTPS in production
CSRF_COOKIE_SAMESITE = 'Strict'
CSRF_COOKIE_SECURE = True
3. Validate Origin and Referer Headers for Sensitive Actions
Add checks in views that perform state-changing operations, even when using Bearer Tokens.
import os
from django.http import HttpResponseForbidden
def validate_request_origin(request):
allowed_origin = os.getenv('ALLOWED_ORIGIN', 'https://yourdomain.com')
origin = request.META.get('HTTP_ORIGIN')
referer = request.META.get('HTTP_REFERER')
if origin and origin != allowed_origin:
return False
if referer and not referer.startswith('https://yourdomain.com'):
return False
return True
def sensitive_action(request):
if not validate_request_origin(request):
return HttpResponseForbidden('Invalid request origin')
# Proceed with token-based logic
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return HttpResponseForbidden('Missing or invalid token')
token = auth_header.split(' ')[1]
# Perform action
return HttpResponse('Action completed')
4. Avoid GET for State-Changing Operations
Ensure endpoints that modify data use POST, PUT, or DELETE and require explicit CSRF tokens or custom header validation.
from django.views.decorators.csrf import csrf_protect
from django.http import JsonResponse
@csrf_protect
def transfer_funds(request):
if request.method != 'POST':
return JsonResponse({'error': 'Method not allowed'}, status=405)
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return JsonResponse({'error': 'Unauthorized'}, status=401)
token = auth_header.split(' ')[1]
# Validate token and perform transfer
return JsonResponse({'status': 'success'})
5. Use Django REST Framework with Token Authentication Safely
When using DRF, ensure session authentication is disabled for API views and that permissions enforce safe framing.
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
class SecureAPIView(APIView):
permission_classes = [IsAuthenticated]
def dispatch(self, request, *args, **kwargs):
# Ensure no session-based CSRF bypass for token auth
if not request.headers.get('Authorization', '').startswith('Bearer '):
return Response({'detail': 'Unauthorized'}, status=status.HTTP_401_UNAUTHORIZED)
return super().dispatch(request, *args, **kwargs)
def post(self, request):
# Action logic
return Response({'result': 'ok'})
6. Middleware for Clickjacking Protection
Implement a simple middleware to strip tokens from Referer headers in cross-origin requests.
class ReferrerPolicyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response['Referrer-Policy'] = 'no-referrer'
response['Permissions-Policy'] = 'geolocation=()'
return response