CSRF Protection¶
Any view using FlaskForm to process the request is already
getting CSRF protection. If you have views that don’t use FlaskForm or make
AJAX requests, use the provided CSRF extension to protect those requests as
well.
Setup¶
To enable CSRF protection globally for a Flask app, register the
CSRFProtect extension.
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
Like other Flask extensions, you can apply it lazily:
csrf = CSRFProtect()
def create_app():
app = Flask(__name__)
csrf.init_app(app)
Note
CSRF protection requires a secret key to securely sign the token. By default
this will use the Flask app’s SECRET_KEY. If you’d like to use a
separate token you can set WTF_CSRF_SECRET_KEY.
Warning
Make sure your webserver cache policy wont’t interfere with the CSRF protection.
If pages are cached longer than the WTF_CSRF_TIME_LIMIT value, then user browsers
may serve cached page including expired CSRF token, resulting in random Invalid
or Expired CSRF errors.
HTML Forms¶
When using a FlaskForm, render the form’s CSRF field like normal.
<form method="post">
{{ form.csrf_token }}
</form>
If the template doesn’t use a FlaskForm, render a hidden input with the
token in the form.
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>
The name attribute must match the WTF_CSRF_FIELD_NAME setting
('csrf_token' by default). Otherwise the request fails with a
Bad Request: CSRF token missing error. Change the setting if you need a
different field name.
HTML Meta Tag¶
For JavaScript clients, the recommended way to expose the token to the page is
to render it in a <meta> tag in the document <head>. This is the
convention used by Rails and recommended by the
OWASP CSRF prevention cheat sheet.
<head>
{{ csrf_meta_tag() }}
</head>
This renders <meta name="csrf-token" content="...">. The attribute name is
configurable via the WTF_CSRF_META_NAME setting, or per-call with the
name argument: {{ csrf_meta_tag(name="authenticity-token") }}.
JavaScript Requests¶
When sending an AJAX request, read the token from the meta tag and send it in
the X-CSRFToken header. This pattern is compatible with a strict
Content-Security-Policy since no inline script is required.
Using fetch:
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch("/api/resource", {
method: "POST",
headers: { "X-CSRFToken": token, "Content-Type": "application/json" },
body: JSON.stringify(data),
});
Using Axios, configure the default header once at startup:
axios.defaults.headers.common["X-CSRFToken"] =
document.querySelector('meta[name="csrf-token"]').content;
Customize the error response¶
When CSRF validation fails, it will raise a CSRFError.
By default this returns a response with the failure reason and a 400 code.
You can customize the error response using Flask’s
errorhandler().
from flask_wtf.csrf import CSRFError
@app.errorhandler(CSRFError)
def handle_csrf_error(e):
return render_template('csrf_error.html', reason=e.description), 400
Exclude views from protection¶
We strongly suggest that you protect all your views with CSRF. But if needed, you can exclude some views using a decorator.
@app.route('/foo', methods=('GET', 'POST'))
@csrf.exempt
def my_handler():
# ...
return 'ok'
You can exclude all the views of a blueprint.
csrf.exempt(account_blueprint)
You can disable CSRF protection in all views by default, by setting
WTF_CSRF_CHECK_DEFAULT to False, and selectively call
protect() only when you need. This also enables you to do some
pre-processing on the requests before checking for the CSRF token.
@app.before_request
def check_csrf():
if not is_oauth(request):
csrf.protect()
Pass apply_exemptions=True to keep @csrf.exempt working in this mode.
The call will skip validation for views and blueprints marked as exempt.
@app.before_request
def check_csrf():
if not is_oauth(request):
csrf.protect(apply_exemptions=True)