Cross Site Request Forgery in Flask with Bearer Tokens
Cross Site Request Forgery in Flask with Bearer Tokens
Cross Site Request Forgery (CSRF) in Flask applications that rely on Bearer Tokens challenges common assumptions about token-based authentication. Bearer Tokens are typically transmitted in HTTP headers, which are not automatically included in browser-initiated requests triggered by third-party sites. However, this does not make CSRF impossible when using Bearer Tokens in Flask. The risk depends on how tokens are stored and whether requests can be forged from the client side. If an application stores a Bearer Token in local storage or session storage and uses JavaScript to attach it to API requests, malicious third-party sites can potentially initiate requests using embedded images, forms, or scripts, leveraging the browser’s automatic inclusion of credentials for the target domain. This is especially relevant when token handling is implemented manually without additional protections. Even with Bearer Tokens, endpoints that accept unsafe methods like POST, PUT, or DELETE without verifying the request origin remain vulnerable when token storage is accessible to malicious scripts. The presence of CORS misconfigurations can further expose these endpoints to unauthorized origins, allowing attackers to craft requests that appear legitimate to the server. For example, an attacker might trick a user’s browser into making a request to /api/transfer with a valid Bearer Token, leading to unauthorized actions. Flask APIs that do not implement anti-CSRF tokens, custom headers, or strict CORS policies expose these attack paths. The use of Bearer Tokens alone does not prevent CSRF; developers must understand how browsers handle credentials and design defenses accordingly. Without proper mitigations, applications risk unauthorized state changes, data manipulation, or privilege escalation through forged requests initiated from malicious sites.
Bearer Tokens-Specific Remediation in Flask
Securing Flask APIs that use Bearer Tokens requires a combination of secure token handling, strict CORS policies, and explicit anti-CSRF measures for browser-based clients. The following practices and code examples demonstrate how to reduce CSRF risk while maintaining token-based authentication.
Secure Token Storage and CORS Configuration
Ensure Bearer Tokens are not stored in locations accessible to JavaScript, such as local storage. Instead, use HttpOnly cookies for token storage when possible, or ensure strict SameSite and Secure flags are applied. Configure CORS to allow only trusted origins and avoid wildcard entries. Below is an example using the flask-cors extension to restrict origins:
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
app.config['CORS_HEADERS'] = 'Content-Type'
CORS(app, resources={r"/api/*": {"origins": ["https://trusted-frontend.com"]}})
@app.route('/api/transfer', methods=['POST'])
def transfer():
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return {'error': 'Unauthorized'}, 401
token = auth_header.split(' ')[1]
# Validate token and process request
return {'status': 'success'}, 200
Anti-CSRF Protection with Custom Headers
Require a custom header (e.g., X-Requested-With or a custom value) for state-changing requests. Because cross-origin requests initiated by forms or scripts cannot set custom headers without CORS preflight approval, this mitigates CSRF for browsers. Below is an example middleware in Flask:
from flask import request, jsonify
@app.before_request
def verify_csrf_for_state_changing():
if request.method in ['POST', 'PUT', 'DELETE', 'PATCH']:
if not request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({'error': 'CSRF protection triggered'}), 403
Stateless Token Validation Example
Validate Bearer Tokens on every request using a public key or introspection endpoint. Ensure tokens have appropriate scopes and are bound to the request context. Here is a minimal route demonstrating token validation:
import jwt
from flask import request, jsonify
PUBLIC_KEY = open('public_key.pem').read()
@app.route('/api/secure-data')
def secure_data():
auth = request.headers.get('Authorization')
if not auth:
return {'error': 'Missing token'}, 401
try:
token = auth.split(' ')[1]
payload = jwt.decode(token, PUBLIC_KEY, algorithms=['RS256'], audience='api.example.com')
# Proceed with request handling using payload['sub']
return {'data': 'secure', 'user': payload['sub']}, 200
except jwt.ExpiredSignatureError:
return {'error': 'Token expired'}, 401
except jwt.InvalidTokenError:
return {'error': 'Invalid token'}, 401
Defense in Depth
Combine these techniques with short token lifetimes, strict scope validation, and monitoring for anomalous request patterns. For non-browser clients or high-security operations, consider adding CSRF tokens alongside Bearer Tokens. Regularly review CORS configurations and token usage to ensure alignment with current security practices.