HIGH cors wildcarddjangobearer tokens

Cors Wildcard in Django with Bearer Tokens

Cors Wildcard in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability

When a Django API uses a wildcard CORS_ALLOW_ALL_ORIGINS (or Access-Control-Allow-Origin: *) while also protecting endpoints with Bearer Token authentication, the combination can unintentionally expose a cross-origin credential leak. The CORS wildcard permits any origin to make requests to your server, and if credentials are allowed, browsers will include cookies as well as HTTP authentication headers such as the Authorization header containing the Bearer token. Because the wildcard permits any domain, a malicious site can issue cross-origin requests that include the user’s Authorization header and read the response if the server does not also restrict origins for credentialed requests.

In concrete terms, a browser will send the Authorization header for a cross-origin request if withCredentials is true (or credentials: 'include' in fetch) and the server responds with an Access-Control-Allow-Credentials: true header. With a wildcard origin, the browser treats the response as readable by the calling page, enabling a cross-site script to harvest the Bearer token from the response or to perform actions on behalf of the authenticated user. This violates the same-origin policy’s isolation and effectively turns the Bearer token into a cross-origin credential.

For Django, common patterns that create this condition include:

  • Setting CORS_ALLOW_ALL_ORIGINS = True while also configuring CORS_ALLOW_CREDENTIALS = True.
  • Using CORSMiddleware with Access-Control-Allow-Origin: * in responses while the client sends Authorization: Bearer <token> and the server validates the token but does not scope access by origin.

An attacker-controlled page can then perform a cross-origin fetch to your API endpoint, and because the server permits any origin and allows credentials, the browser includes the Bearer token. Even if the endpoint returns data intended only for the authenticated user, the attacker can read it. This situation is especially dangerous when the Bearer token is used for authentication and the API does not validate the Origin header or enforce strict referrer checks, because the token is effectively shared cross-origin.

The interaction between CORS and Bearer tokens is nuanced by the fact that the CORS standard treats Authorization headers as "forbidden headers" for JavaScript to set, but they can be sent by the browser automatically when credentials are included. Therefore, simply requiring Bearer tokens does not prevent a browser from including them cross-origin if the server signals that it is safe to do so via permissive CORS settings.

Bearer Tokens-Specific Remediation in Django — concrete code fixes

To secure a Django API that uses Bearer tokens while also supporting cross-origin requests, you must align CORS configuration with strict origin allowlisting and careful handling of credentials. Begin by disabling the wildcard for credentialed requests and explicitly listing trusted origins. This ensures that browsers only send the Authorization header to known origins, preventing leakage to malicious sites.

Example secure Django settings using the django-cors-headers package:

import os

# settings.py
CORS_ALLOWED_ORIGINS = [
    "https://app.yourdomain.com",
    "https://admin.yourdomain.com",
]

CORS_ALLOW_CREDENTIALS = True

# Ensure that your authentication does not rely on wildcard origins
CSRF_COOKIE_SAMESITE = 'Strict'
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True

In this configuration, only the listed origins can make authenticated requests that include cookies or Authorization headers. The server will respond with Access-Control-Allow-Origin set to the requesting origin (not *) and include Access-Control-Allow-Credentials: true, which is required for the browser to expose the response to frontend JavaScript.

When validating Bearer tokens, ensure that token verification occurs after CORS and authentication middleware so that the origin check is applied to authenticated requests. Here is an example of a view that explicitly checks both the Authorization header and the Origin header for additional safety:

from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.utils.decorators import method_decorator
from corsheaders.middleware import CORSMiddleware

@method_decorator(require_http_methods(["GET"]), name='dispatch')
class SecureApiView(View):
    def dispatch(self, request, *args, **middleware):
        auth = request.headers.get('Authorization', '')
        if not auth.startswith('Bearer '):
            return JsonResponse({'error': 'Unauthorized'}, status=401)
        token = auth.split(' ')[1]
        if not self.is_valid_token(token):
            return JsonResponse({'error': 'Invalid token'}, status=401)
        # Optionally validate request origin matches an allowed origin
        origin = request.headers.get('Origin', '')
        allowed_origins = os.getenv('ALLOWED_CORS_ORIGINS', '').split(',')
        if origin not in allowed_origins:
            return JsonResponse({'error': 'Forbidden'}, status=403)
        return super().dispatch(request, *args, **kwargs)

    def is_valid_token(self, token):
        # Replace with your token validation logic, e.g., JWT decode
        return token == 'expected-secure-token'

For SPAs, ensure that the frontend sets credentials: 'include' intentionally and that the backend does not echo the Origin header blindly. Combine this with short-lived Bearer tokens and refresh token rotation to reduce the impact of token leakage. You can also use the Vary: Origin header to prevent shared caches from serving a credentialed response to an unauthorized origin.

If you prefer to use the Django REST Framework, the same principles apply with configured permission classes and CORS settings:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
}

In summary, the key remediation steps are: replace wildcard CORS with a strict allowlist, require Access-Control-Allow-Credentials: true only for those origins, validate Bearer tokens on every request, and optionally add origin validation within your views. This prevents the Bearer token from being treated as a cross-origin credential while still allowing legitimate browser-based clients to authenticate.

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

Can I safely use CORS wildcard if my API does not send the Authorization header?
If your API never requires Bearer tokens or cookies for authentication, a CORS wildcard is less risky because the Authorization header is not needed cross-origin. However, you should still restrict origins if your API returns sensitive data, because a wildcard can enable other cross-origin attacks such as CSRF or data exfiltration via other headers or methods.
Does including the Authorization header in CORS requests automatically expose my Bearer token?
Not automatically; exposure occurs when a permissive CORS configuration (such as wildcard origins with credentials allowed) permits a malicious page to read the response containing the token. If you use strict origin allowlisting, require credentials only for known origins, and avoid reflecting sensitive data to untrusted origins, the Bearer token is not inadvertently exposed to cross-origin scripts.