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.


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 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.


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 }}

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() }}"/>

JavaScript Requests

When sending an AJAX request, add the X-CSRFToken header to it. For example, in jQuery you can configure all requests to send the token.

<script type="text/javascript">
    var csrf_token = "{{ csrf_token() }}";

        beforeSend: function(xhr, settings) {
            if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrf_token);

In Axios you can set the header for all requests with axios.defaults.headers.common.

<script type="text/javascript">
    axios.defaults.headers.common["X-CSRFToken"] = "{{ csrf_token() }}";

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

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'))
def my_handler():
    # ...
    return 'ok'

You can exclude all the views of a 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.

def check_csrf():
    if not is_oauth(request):