Cors Wildcard in Flask with Bearer Tokens
Cors Wildcard in Flask with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Cross-origin resource sharing (CORS) misconfigurations are common in Flask APIs, especially when authentication relies on bearer tokens. A wildcard CORS policy, typically set via Access-Control-Allow-Origin: *, can unintentionally expose protected endpoints when combined with bearer token authentication. In Flask, this often occurs when developers use extensions like flask-cors and apply a global wildcard without accounting for credentialed requests.
When an API uses bearer tokens in the Authorization header, the browser’s CORS preflight and simple requests follow specific rules. If Access-Control-Allow-Origin: * is returned while the request includes credentials (such as an Authorization header), the browser treats the response as a CORS error and blocks the frontend from reading the response. This does not prevent a non-browser client (like curl or Postman) from calling the endpoint, but it does break legitimate web clients and can create confusion about whether the API is truly protected.
More critically, a wildcard origin can pair with permissive CORS methods and headers to expose token-handling logic to any website. For example, if Access-Control-Allow-Methods includes unsafe methods and Access-Control-Allow-Headers includes Authorization without strict validation, an attacker-controlled site can probe the API via JavaScript. While the browser blocks the frontend from reading the response, the preflight and actual requests still reach the server, potentially aiding reconnaissance or token leakage via logs or error messages.
Consider a Flask route that validates bearer tokens manually:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.before_request
def cors_headers():
# Dangerous: wildcard with bearer usage
origin = request.headers.get('Origin')
if origin:
# This allows any origin when bearer tokens are used
response = app.response_class()
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
@app.route('/api/me')
def me():
auth = request.headers.get('Authorization')
if not auth or not auth.startswith('Bearer '):
return jsonify({'error': 'Unauthorized'}), 401
token = auth.split(' ')[1]
# Token validation omitted for brevity
return jsonify({'ok': True})
In this pattern, the wildcard origin is applied regardless of the token’s validity or origin. A safer approach is to mirror the request origin when it is known and trusted, and to avoid wildcard origins when credentials are involved. Even with bearer tokens, CORS should be as restrictive as possible: specific origins, limited methods, and explicit headers.
Another subtle risk involves preflight caching. Browsers cache preflight responses, and a wildcard policy can persist across deployments, keeping overly permissive CORS active longer than intended. This can delay detection of misconfigurations and prolong exposure. Combining this with bearer tokens means token validation logic might be assumed to be enforced by CORS, when in fact CORS only governs browser-based cross-origin access and does not replace server-side authorization checks.
In summary, the combination of CORS wildcard and bearer tokens in Flask creates a misconfiguration where the API appears protected by authentication but is actually exposed to cross-origin abuse in browser contexts. It can lead to CORS errors for legitimate web clients and may aid attacker reconnaissance. The fix is not to remove bearer tokens but to tighten CORS: avoid wildcards, explicitly list origins, and ensure server-side authorization remains the ultimate gatekeeper.
Bearer Tokens-Specific Remediation in Flask — concrete code fixes
Remediation centers on precise CORS configuration and robust token validation. For Flask, prefer flask-cors with fine-grained controls or manually set headers to avoid wildcards when Authorization is present. Below are two concrete, secure patterns.
Pattern 1: Conditional CORS without wildcards
Instead of a global wildcard, set Access-Control-Allow-Origin to the request origin only when it matches an allowlist, and never when the request is unauthenticated or ambiguous.
from flask import Flask, request, jsonify, make_response
app = Flask(__name__)
ALLOWED_ORIGINS = {
'https://app.example.com',
'https://admin.example.com',
}
@app.after_request
def apply_cors(response):
origin = request.headers.get('Origin')
if origin in ALLOWED_ORIGINS:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
@app.route('/api/me')
def me():
auth = request.headers.get('Authorization')
if not auth or not auth.startswith('Bearer '):
return jsonify({'error': 'Unauthorized'}), 401
token = auth.split(' ')[1]
# Validate token here (e.g., verify signature and claims)
return jsonify({'ok': True})
Pattern 2: Using flask-cors with resource-specific rules
If you use flask-cors, configure it to avoid wildcards and to handle credentials explicitly.
from flask import Flask, jsonify
from flask_cors import CORS, cross_origin
app = Flask(__name__)
# Global config without wildcard
CORS(app, resources={r&api/*: {"origins": ["https://app.example.com", "https://admin.example.com"], "supports_credentials": True}})
@app.route('/api/me')
def me():
auth = request.headers.get('Authorization')
if not auth or not auth.startswith('Bearer '):
return jsonify({'error': 'Unauthorized'}), 401
token = auth.split(' ')[1]
# Validate token here
return jsonify({'ok': True})
In both examples, the key remediation points are:
- Never return
Access-Control-Allow-Origin: *when the request includes bearer tokens or credentials. - Validate the bearer token on the server on every request; do not rely on CORS to enforce authorization.
- Explicitly list allowed origins and methods, and set
Access-Control-Allow-Credentials: trueonly when necessary. - Ensure token validation logic is independent of CORS and runs for every request.
These patterns ensure that CORS does not inadvertently weaken bearer token protections while still allowing controlled cross-origin access for your web clients.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |