Flask-WTF Setup & CSRF Token Protection0%

Flask-WTF Setup & CSRF Token Protection

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

Flask-WTF Setup & CSRF Token Protection

Handling HTML form submissions safely is one of the most critical security aspects of web development. Naive form handling without validation exposes applications to SQL injection, malicious scripts, and Cross-Site Request Forgery (CSRF) attacks.

The Flask-WTF extension integrates WTForms with Flask, providing declarative form classes, field type casting, built-in input sanitization, and automatic CSRF token protection.


1. What is Cross-Site Request Forgery (CSRF)?

In a CSRF attack, an attacker tricks a victim's authenticated browser into executing unwanted actions on a trusted website:

Visual Architecture & Process Flow

How data and code flow step-by-step

Flowchart
Step 1
1. User logs into your bank or portal
Step 2
Session cookie stored in browser.

The Synchronizer Token Defense:

To prevent CSRF, Flask-WTF generates a cryptographically unique, unpredictable token bound to the user's active session. When submitting a form, the client must transmit this token. If the token is missing or mismatched, Flask immediately aborts the request with an HTTP 400 Bad Request (CSRF Token Missing or Invalid).


2. Installing and Configuring Flask-WTF

Install the extension:

Bash / Terminal
pip install flask-wtf

Flask-WTF requires a secret key configured on the application to cryptographically sign CSRF tokens:

Python
import os
from flask import Flask
from flask_wtf.csrf import CSRFProtect
 
app = Flask(__name__)
 
# Essential: Must be a strong, unpredictable random string
app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "dev-fallback-key-9f42c7104b2a")
 
# Enable global CSRF protection across all POST/PUT/PATCH/DELETE requests
csrf = CSRFProtect(app)

3. Rendering CSRF Tokens in HTML Forms

When rendering raw HTML forms without WTForms form classes, inject the CSRF token using csrf_token():

HTML
<form method="POST" action="/submit-feedback">
<!-- Hidden CSRF security input -->
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
 
<div class="mb-4">
<label for="feedback">Your Feedback:</label>
<textarea id="feedback" name="feedback" required></textarea>
</div>
 
<button type="submit">Submit Feedback</button>
</form>

4. Protecting AJAX and Fetch API Requests

For Single Page Apps or JavaScript fetch() requests, pass the CSRF token via the X-CSRFToken HTTP header:

HTML
<!-- Inject CSRF token into HTML meta tag -->
<meta name="csrf-token" content="{{ csrf_token() }}">
 
<script>
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
 
async function postComment(commentText) {
const response = await fetch('/api/comments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken // Verified automatically by CSRFProtect!
},
body: JSON.stringify({ comment: commentText })
});
return await response.json();
}
</script>

5. Exempting Webhooks with @csrf.exempt

External machine-to-machine services (such as Stripe payment webhooks or GitHub Actions webhooks) cannot provide a browser session CSRF token. Exempt specific endpoints using the @csrf.exempt decorator:

Python
@app.route("/webhooks/stripe", methods=["POST"])
@csrf.exempt
def stripe_webhook():
# Verify signature using Stripe cryptographic headers instead of CSRF tokens
return {"status": "Webhook received"}, 200

Interactive Quiz

Practice Quiz

Question 1
What application setting is mandatory for Flask-WTF to sign CSRF tokens?
Question 2
What HTTP status code does Flask-WTF return if a POST request fails CSRF verification?
Question 3
Which HTTP header does CSRFProtect inspect for AJAX / fetch requests?
Question 4
Why must payment gateway webhooks (e.g. Stripe, Razorpay) be marked with @csrf.exempt?
Question 5
How do you output the raw CSRF token value inside an HTML template?
Next Lesson

Form Classes & Built-In Field Validators

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext