0
# CSRF Protection
1
2
Built-in cross-site request forgery protection with session-based token generation and validation, configurable through form meta options. WTForms provides a comprehensive CSRF protection system that integrates seamlessly with web frameworks.
3
4
## Capabilities
5
6
### CSRF Field Types
7
8
Special field types for handling CSRF tokens.
9
10
```python { .api }
11
class CSRFTokenField(HiddenField):
12
"""
13
Hidden field for CSRF tokens.
14
Automatically renders current token and never populates object data.
15
Extends HiddenField with CSRF-specific behavior.
16
"""
17
def __init__(self, label=None, validators=None, **kwargs): ...
18
19
def populate_obj(self, obj, name):
20
"""Override to prevent populating object attributes."""
21
pass
22
```
23
24
### Core CSRF Classes
25
26
Abstract base and concrete implementations for CSRF protection.
27
28
```python { .api }
29
class CSRF:
30
"""
31
Abstract base class for CSRF protection implementations.
32
Defines the interface for CSRF token generation and validation.
33
"""
34
def setup_form(self, form) -> list:
35
"""
36
Add CSRF fields to form.
37
38
Parameters:
39
- form: Form instance to protect
40
41
Returns:
42
list: List of (field_name, unbound_field) tuples to add to form
43
"""
44
45
def generate_csrf_token(self, csrf_token_field) -> str:
46
"""
47
Generate CSRF token (abstract method).
48
49
Parameters:
50
- csrf_token_field: CSRFTokenField instance
51
52
Returns:
53
str: Generated CSRF token
54
"""
55
raise NotImplementedError()
56
57
def validate_csrf_token(self, form, field):
58
"""
59
Validate CSRF token (abstract method).
60
61
Parameters:
62
- form: Form instance
63
- field: CSRFTokenField instance
64
65
Raises:
66
ValidationError: If token is invalid
67
"""
68
raise NotImplementedError()
69
70
class SessionCSRF(CSRF):
71
"""
72
Session-based CSRF implementation using HMAC-SHA1.
73
Stores token data in session and validates against generated tokens.
74
75
Parameters:
76
- secret_key: Secret key for HMAC generation (bytes)
77
- timeout: Token timeout in seconds (None for no timeout)
78
"""
79
def __init__(self, secret_key, timeout=None): ...
80
81
def generate_csrf_token(self, csrf_token_field) -> str:
82
"""
83
Generate HMAC-SHA1 based CSRF token.
84
85
Returns:
86
str: Base64 encoded token with timestamp and hash
87
"""
88
89
def validate_csrf_token(self, form, field):
90
"""
91
Validate CSRF token against session data and timestamp.
92
93
Raises:
94
ValidationError: If token is missing, expired, or invalid
95
"""
96
```
97
98
## CSRF Usage Examples
99
100
### Basic CSRF Protection
101
102
```python
103
from wtforms import Form, StringField, validators
104
from wtforms.csrf.session import SessionCSRF
105
from wtforms.meta import DefaultMeta
106
107
class SecureForm(Form):
108
class Meta(DefaultMeta):
109
csrf = True
110
csrf_secret = b'your-secret-key-here' # Must be bytes
111
csrf_context = session # Session-like object (Flask session, etc.)
112
csrf_field_name = 'csrf_token' # Default field name
113
114
message = StringField('Message', [validators.DataRequired()])
115
116
# Usage with Flask
117
@app.route('/contact', methods=['GET', 'POST'])
118
def contact():
119
form = SecureForm(formdata=request.form, meta={'csrf_context': session})
120
121
if form.validate():
122
# Form passed CSRF validation
123
process_message(form.message.data)
124
return redirect('/success')
125
126
return render_template('contact.html', form=form)
127
```
128
129
### Template Usage
130
131
```html
132
<!-- In your HTML template -->
133
<form method="POST">
134
{{ form.csrf_token }} <!-- Hidden CSRF field -->
135
{{ form.message.label }} {{ form.message() }}
136
<input type="submit" value="Submit">
137
</form>
138
139
<!-- Or explicitly render CSRF token -->
140
<form method="POST">
141
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
142
<!-- Other form fields -->
143
</form>
144
```
145
146
### Custom CSRF Configuration
147
148
```python
149
from wtforms.csrf.session import SessionCSRF
150
151
class CustomCSRFForm(Form):
152
class Meta:
153
csrf = True
154
csrf_secret = b'super-secret-key'
155
csrf_field_name = 'authenticity_token' # Custom field name
156
csrf_context = None # Will be set per request
157
csrf_class = SessionCSRF # Explicit CSRF class
158
159
name = StringField('Name')
160
email = StringField('Email')
161
162
# Per-request context setting
163
def create_form(request):
164
form = CustomCSRFForm(
165
formdata=request.form,
166
meta={'csrf_context': request.session}
167
)
168
return form
169
```
170
171
### CSRF with Timeout
172
173
```python
174
from wtforms.csrf.session import SessionCSRF
175
176
class TimedCSRFForm(Form):
177
class Meta:
178
csrf = True
179
csrf_secret = b'your-secret-key'
180
csrf_context = session
181
182
# Custom CSRF with 30 minute timeout
183
@property
184
def csrf_class(self):
185
return SessionCSRF(secret_key=self.Meta.csrf_secret, timeout=1800)
186
187
data = StringField('Data')
188
189
# Tokens will expire after 30 minutes
190
form = TimedCSRFForm(formdata=request.form)
191
if form.validate():
192
# Process valid form with non-expired token
193
pass
194
```
195
196
### Multiple Forms with CSRF
197
198
```python
199
class LoginForm(Form):
200
class Meta:
201
csrf = True
202
csrf_secret = b'login-secret'
203
csrf_field_name = 'login_token'
204
csrf_context = session
205
206
username = StringField('Username')
207
password = StringField('Password')
208
209
class RegistrationForm(Form):
210
class Meta:
211
csrf = True
212
csrf_secret = b'register-secret' # Different secret
213
csrf_field_name = 'register_token' # Different field name
214
csrf_context = session
215
216
username = StringField('Username')
217
email = StringField('Email')
218
password = StringField('Password')
219
220
# Both forms can coexist with different CSRF tokens
221
@app.route('/auth', methods=['GET', 'POST'])
222
def auth():
223
login_form = LoginForm(formdata=request.form, prefix='login')
224
register_form = RegistrationForm(formdata=request.form, prefix='register')
225
226
if 'login_submit' in request.form and login_form.validate():
227
# Process login
228
pass
229
elif 'register_submit' in request.form and register_form.validate():
230
# Process registration
231
pass
232
233
return render_template('auth.html',
234
login_form=login_form,
235
register_form=register_form)
236
```
237
238
### Custom CSRF Implementation
239
240
```python
241
from wtforms.csrf.core import CSRF, CSRFTokenField
242
from wtforms.validators import ValidationError
243
import hmac
244
import hashlib
245
import time
246
import base64
247
248
class DatabaseCSRF(CSRF):
249
"""Custom CSRF implementation using database storage."""
250
251
def __init__(self, secret_key, db_session, timeout=3600):
252
self.secret_key = secret_key
253
self.db_session = db_session
254
self.timeout = timeout
255
256
def generate_csrf_token(self, csrf_token_field):
257
# Generate unique token
258
timestamp = str(int(time.time()))
259
user_id = str(getattr(csrf_token_field.form, 'current_user_id', 0))
260
261
# Create token data
262
token_data = f"{timestamp}:{user_id}"
263
signature = hmac.new(
264
self.secret_key,
265
token_data.encode('utf-8'),
266
hashlib.sha256
267
).hexdigest()
268
269
token = base64.b64encode(f"{token_data}:{signature}".encode()).decode()
270
271
# Store in database
272
csrf_record = CSRFToken(token=token, created_at=time.time())
273
self.db_session.add(csrf_record)
274
self.db_session.commit()
275
276
return token
277
278
def validate_csrf_token(self, form, field):
279
if not field.data:
280
raise ValidationError('CSRF token missing')
281
282
try:
283
# Decode token
284
decoded = base64.b64decode(field.data).decode()
285
timestamp, user_id, signature = decoded.split(':')
286
287
# Verify signature
288
expected_data = f"{timestamp}:{user_id}"
289
expected_signature = hmac.new(
290
self.secret_key,
291
expected_data.encode('utf-8'),
292
hashlib.sha256
293
).hexdigest()
294
295
if not hmac.compare_digest(signature, expected_signature):
296
raise ValidationError('Invalid CSRF token')
297
298
# Check timeout
299
if time.time() - int(timestamp) > self.timeout:
300
raise ValidationError('CSRF token expired')
301
302
# Verify token exists in database
303
csrf_record = self.db_session.query(CSRFToken).filter_by(
304
token=field.data
305
).first()
306
307
if not csrf_record:
308
raise ValidationError('CSRF token not found')
309
310
# Remove used token (one-time use)
311
self.db_session.delete(csrf_record)
312
self.db_session.commit()
313
314
except (ValueError, TypeError):
315
raise ValidationError('Malformed CSRF token')
316
317
class DatabaseProtectedForm(Form):
318
class Meta:
319
csrf = True
320
csrf_class = DatabaseCSRF
321
csrf_secret = b'database-csrf-secret'
322
323
def __init__(self, db_session, *args, **kwargs):
324
# Inject database session into CSRF
325
meta = kwargs.get('meta', {})
326
meta['csrf_class'] = DatabaseCSRF(
327
secret_key=self.Meta.csrf_secret,
328
db_session=db_session
329
)
330
kwargs['meta'] = meta
331
super().__init__(*args, **kwargs)
332
333
data = StringField('Data')
334
```
335
336
### CSRF Error Handling
337
338
```python
339
from wtforms.validators import ValidationError
340
341
class CSRFProtectedForm(Form):
342
class Meta:
343
csrf = True
344
csrf_secret = b'secret-key'
345
csrf_context = session
346
347
message = StringField('Message')
348
349
@app.route('/submit', methods=['POST'])
350
def submit():
351
form = CSRFProtectedForm(formdata=request.form)
352
353
try:
354
if form.validate():
355
# Process valid form
356
return jsonify({'status': 'success'})
357
else:
358
# Handle validation errors
359
errors = {}
360
for field_name, field_errors in form.errors.items():
361
if field_name == 'csrf_token':
362
# CSRF-specific error handling
363
return jsonify({
364
'status': 'error',
365
'message': 'Security token invalid. Please refresh and try again.'
366
}), 403
367
errors[field_name] = field_errors
368
369
return jsonify({'status': 'error', 'errors': errors}), 400
370
371
except ValidationError as e:
372
# Handle CSRF validation exceptions
373
return jsonify({
374
'status': 'error',
375
'message': 'Security validation failed'
376
}), 403
377
```
378
379
### CSRF with AJAX
380
381
```python
382
# Server-side: Provide CSRF token for AJAX requests
383
@app.route('/api/csrf-token')
384
def csrf_token():
385
form = CSRFProtectedForm() # Create form to get token
386
return jsonify({'csrf_token': form.csrf_token.data})
387
388
# Client-side JavaScript
389
fetch('/api/csrf-token')
390
.then(response => response.json())
391
.then(data => {
392
// Include CSRF token in subsequent requests
393
fetch('/api/submit', {
394
method: 'POST',
395
headers: {
396
'Content-Type': 'application/json',
397
'X-CSRF-Token': data.csrf_token
398
},
399
body: JSON.stringify({message: 'Hello World'})
400
});
401
});
402
```
403
404
### Framework Integration Examples
405
406
#### Flask Integration
407
408
```python
409
from flask import Flask, session, request
410
from wtforms.csrf.session import SessionCSRF
411
412
app = Flask(__name__)
413
app.secret_key = 'your-flask-secret'
414
415
class FlaskForm(Form):
416
class Meta:
417
csrf = True
418
csrf_secret = app.secret_key.encode()
419
csrf_context = session
420
421
def __init__(self, *args, **kwargs):
422
super().__init__(*args, **kwargs)
423
424
# Usage
425
@app.route('/form', methods=['GET', 'POST'])
426
def form_view():
427
form = FlaskForm(formdata=request.form)
428
if form.validate():
429
# Process form
430
pass
431
return render_template('form.html', form=form)
432
```
433
434
#### Django Integration
435
436
```python
437
from django.middleware.csrf import get_token
438
439
class DjangoForm(Form):
440
class Meta:
441
csrf = True
442
csrf_secret = settings.SECRET_KEY.encode()
443
444
def __init__(self, request, *args, **kwargs):
445
# Use Django's CSRF token
446
meta = kwargs.get('meta', {})
447
meta['csrf_context'] = {
448
'csrf_token': get_token(request)
449
}
450
kwargs['meta'] = meta
451
super().__init__(*args, **kwargs)
452
453
# Usage in Django view
454
def form_view(request):
455
form = DjangoForm(request, data=request.POST or None)
456
if form.validate():
457
# Process form
458
pass
459
return render(request, 'form.html', {'form': form})
460
```
461
462
### CSRF Configuration Best Practices
463
464
```python
465
import os
466
from wtforms import Form
467
468
class ProductionForm(Form):
469
class Meta:
470
# Enable CSRF protection
471
csrf = True
472
473
# Use environment variable for secret
474
csrf_secret = os.environ.get('CSRF_SECRET_KEY', '').encode()
475
476
# Custom field name for security through obscurity
477
csrf_field_name = 'authenticity_token'
478
479
# Session context will be set per request
480
csrf_context = None
481
482
def __init__(self, *args, **kwargs):
483
# Validate configuration
484
if not self.Meta.csrf_secret:
485
raise ValueError("CSRF_SECRET_KEY environment variable required")
486
487
super().__init__(*args, **kwargs)
488
489
# Environment setup
490
# export CSRF_SECRET_KEY="your-long-random-secret-key"
491
```