Cross Site Request Forgery in Django with Bearer Tokens
Cross Site Request Forgery in Django with Bearer Tokens
Cross Site Request Forgery (CSRF) occurs when an attacker tricks a victim into executing unwanted actions on a web application where the victim is authenticated. In Django, CSRF protection is primarily designed for cookie-based sessions and relies on the SameSite attribute and CSRF tokens for forms and AJAX requests that use session authentication. When APIs use Bearer tokens (e.g., passed in the Authorization header as Authorization: Bearer <token>) for authentication, the protections provided by Django’s CSRF middleware do not apply by default because the CSRF middleware checks for the presence of a CSRF token in cookies or request bodies, not the Authorization header.
In a typical Django REST API setup using token-based authentication, views may rely on request.user being set by a custom authentication class (such as one that validates Bearer tokens). If these views also accept unsafe HTTP methods like POST, PUT, or DELETE and do not independently verify the intent of the requestor, they can be vulnerable to CSRF-like attacks. For example, an authenticated user visiting a malicious site could trigger a crafted request from their browser to the API endpoint, and if the API relies solely on the presence of a valid Bearer token in the header (which the browser will include automatically if the request is cross-origin and credentials are permitted), the action may be executed without additional verification.
Consider an endpoint that changes the user’s email address via a POST request. If the endpoint only checks for a valid Bearer token and does not require a CSRF token or a same-origin check, an attacker could host a page with an image or script tag that sends a request to https://api.example.com/users/me/email. Because the user’s browser includes the Bearer token automatically (if configured to send credentials cross-origin), the server processes the request as legitimate. This scenario is especially relevant when using mobile apps or SPAs that store tokens in localStorage and configure HTTP clients to include the Authorization header, and inadvertently allow cross-origin requests without additional safeguards.
The interaction between Bearer tokens and CSRF becomes a concern when APIs are used in contexts where browsers automatically send credentials, such as with withCredentials in XMLHttpRequest or credentials: 'include' in fetch requests. If the API is also accessible from a browser context and lacks explicit anti-CSRF mechanisms for non-form requests, an attacker may leverage social engineering to induce the victim’s browser to perform state-changing operations. While traditional Django CSRF protection does not cover header-based authentication, developers must implement additional checks such as verifying the Origin or Referer headers, requiring custom anti-CSRF tokens for sensitive operations, or using SameSite cookie attributes where cookies are involved.
It is important to note that Bearer tokens are commonly used in scenarios where CSRF protection is not required, such as when the API is consumed by non-browser clients or when CORS policies strictly limit origins and do not allow credentials. However, when browser-based clients are involved, relying solely on Bearer tokens in the Authorization header without additional CSRF mitigation can expose the application to unauthorized actions. Security testing tools like middleBrick scan for such misconfigurations as part of its 12 security checks, including Authentication and BOLA/IDOR assessments, to identify whether APIs using Bearer tokens are adequately protected against cross-origin abuse.
Bearer Tokens-Specific Remediation in Django
To secure Django APIs that use Bearer tokens, implement explicit CSRF mitigation strategies tailored to token-based authentication. One effective approach is to require a custom anti-CSRF header (e.g., X-CSRFToken) for any state-changing request, and ensure that this header is validated independently of the Cookie-based CSRF token. This method is common in SPAs that manage their own token storage and transmission. For example, configure your authentication class to extract the token from the Authorization header and then enforce that requests modifying state include a matching CSRF token sent via a header, not relying on cookies.
Another remediation strategy involves validating the Origin and Referer headers for sensitive endpoints. While not foolproof due to potential header omission or spoofing in non-browser contexts, this adds a layer of protection against simple cross-origin requests from browsers. Combine this with ensuring that CORS configurations do not allow credentials unless necessary, and apply strict CORS rules using packages like django-cors-headers to limit origins explicitly.
For APIs that serve both browser and non-browser clients, consider using different authentication schemes: continue using Bearer tokens for non-browser clients where CSRF is irrelevant, and enforce session-based authentication with traditional CSRF tokens for browser-based interactions. This separation ensures that each channel uses the appropriate security controls. Below is an example of a custom authentication class in Django REST Framework that extracts a Bearer token and a separate CSRF check for sensitive views.
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from django.http import HttpResponseForbidden
import re
class BearerTokenAuthentication(BaseAuthentication):
def authenticate(self, request):
auth = request.headers.get('Authorization')
if not auth or not auth.startswith('Bearer '):
return None
token = auth.split(' ')[1]
# Validate token logic here, e.g., lookup in a model
if not self.is_valid_token(token):
raise AuthenticationFailed('Invalid token')
user = self.get_user_from_token(token)
return (user, token)
def is_valid_token(self, token):
# Replace with actual validation
return token == 'valid_token_example'
def get_user_from_token(self, token):
# Replace with actual user retrieval
from django.contrib.auth.models import User
return User.objects.filter(username='apiuser').first()
# In a view requiring CSRF protection for token-based requests
def sensitive_action(request):
# Require a custom CSRF header when using Bearer tokens
csrf_token = request.headers.get('X-CSRFToken')
if csrf_token != request.session.get('_csrf_token'): # Example session-based check
return HttpResponseForbidden('CSRF token mismatch')
# Proceed with action
return HttpResponse('Action executed')
Additionally, for endpoints that must accept requests from browsers with Bearer tokens, enforce strict CORS and SameSite policies. Configure your authentication to ignore CSRF checks only when you are certain the request is not susceptible to cross-origin abuse, and always apply the principle of least privilege to token scopes. Regularly audit your API endpoints using tools like middleBrick to verify that authentication mechanisms and CSRF protections align with the actual attack surface, including checks for BOLA/IDOR and unsafe consumption patterns.