Form rendering, validation, and CSRF protection for Flask with WTForms.
72
Cross-Site Request Forgery (CSRF) protection for Flask applications with automatic token generation, validation, and request processing. Flask-WTF provides both global application-level protection and manual token handling for custom scenarios.
Main CSRF protection extension that integrates with Flask's request processing cycle. Provides automatic protection for specified HTTP methods and allows exemption of specific views or blueprints.
class CSRFProtect:
def __init__(self, app: Flask = None):
"""
Initialize CSRF protection.
Args:
app: Flask application instance (optional for factory pattern)
"""
def init_app(self, app: Flask):
"""
Initialize CSRF protection with Flask app (factory pattern).
Args:
app: Flask application instance
"""
def exempt(self, view):
"""
Mark a view function or blueprint exempt from CSRF protection.
Args:
view: View function, blueprint, or string view name
Returns:
The view function or blueprint (for use as decorator)
"""
def protect(self):
"""
Manually protect the current request with CSRF validation.
Raises CSRFError if validation fails.
"""Generate CSRF tokens for use in templates and views. Tokens are cached per request and stored in the session for validation.
def generate_csrf(secret_key: str = None, token_key: str = None) -> str:
"""
Generate a CSRF token for the current request.
Args:
secret_key: Secret key for signing (defaults to WTF_CSRF_SECRET_KEY or app.secret_key)
token_key: Session key for token storage (defaults to WTF_CSRF_FIELD_NAME)
Returns:
Signed CSRF token string
"""Manually validate CSRF tokens in custom scenarios where automatic protection is not sufficient.
def validate_csrf(
data: str,
secret_key: str = None,
time_limit: int = None,
token_key: str = None
):
"""
Validate a CSRF token against the session token.
Args:
data: The signed CSRF token to validate
secret_key: Secret key for verification (defaults to WTF_CSRF_SECRET_KEY)
time_limit: Token expiration time in seconds (defaults to WTF_CSRF_TIME_LIMIT)
token_key: Session key for stored token (defaults to WTF_CSRF_FIELD_NAME)
Raises:
ValidationError: If token is missing, expired, invalid, or doesn't match
"""Exception raised when CSRF validation fails. Inherits from Werkzeug's BadRequest for proper HTTP error handling.
class CSRFError(BadRequest):
"""
CSRF validation failed error.
Generates 400 Bad Request response by default.
Customize response by registering error handler with Flask.
"""
description: str = "CSRF validation failed."from flask import Flask
from flask_wtf import CSRFProtect
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
# Enable CSRF protection for all views
csrf = CSRFProtect(app)
# Or using factory pattern
csrf = CSRFProtect()
csrf.init_app(app)# Exempt a single view
@app.route('/api/public', methods=['POST'])
@csrf.exempt
def public_api():
return {'status': 'ok'}
# Exempt an entire blueprint
from flask import Blueprint
api = Blueprint('api', __name__)
csrf.exempt(api)
# Exempt by string name
csrf.exempt('main.upload_file')from flask import render_template
from flask_wtf.csrf import generate_csrf, validate_csrf
@app.route('/custom-form')
def custom_form():
token = generate_csrf()
return render_template('custom.html', csrf_token=token)
@app.route('/validate-token', methods=['POST'])
def validate_token():
token = request.form.get('csrf_token')
try:
validate_csrf(token)
return 'Valid token'
except ValidationError as e:
return f'Invalid token: {e}', 400CSRF tokens are automatically available in Jinja2 templates:
<!-- Automatic token function -->
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<!-- form fields -->
</form>
<!-- Via context processor -->
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<!-- form fields -->
</form>app.config['SECRET_KEY'] = 'your-secret-key-here'# CSRF protection settings
app.config['WTF_CSRF_ENABLED'] = True # Enable/disable protection
app.config['WTF_CSRF_SECRET_KEY'] = 'csrf-specific-key' # Separate CSRF key
app.config['WTF_CSRF_FIELD_NAME'] = 'csrf_token' # Token field name
app.config['WTF_CSRF_TIME_LIMIT'] = 3600 # Token expiration (seconds)
# HTTP method protection
app.config['WTF_CSRF_METHODS'] = ['POST', 'PUT', 'PATCH', 'DELETE']
# Token sources (form data and headers)
app.config['WTF_CSRF_HEADERS'] = ['X-CSRFToken', 'X-CSRF-Token']
# Request processing settings
app.config['WTF_CSRF_CHECK_DEFAULT'] = True # Auto-check requests
app.config['WTF_CSRF_SSL_STRICT'] = True # Enforce referrer on HTTPS@app.errorhandler(CSRFError)
def handle_csrf_error(e):
return render_template('csrf_error.html'), 400For JavaScript/AJAX requests, include CSRF token in headers:
// Get token from meta tag
const token = document.querySelector('meta[name=csrf-token]').getAttribute('content');
// Include in AJAX requests
fetch('/api/endpoint', {
method: 'POST',
headers: {
'X-CSRFToken': token,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});Template meta tag:
<meta name="csrf-token" content="{{ csrf_token() }}">Install with Tessl CLI
npx tessl i tessl/pypi-flask-wtfevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10