Flask-SeaSurf¶
SeaSurf is a Flask extension for preventing cross-site request forgery (CSRF).
CSRF vulnerabilities have been found in large and popular sites such as YouTube. These attacks are problematic because the mechanism they use is relatively easy to exploit. This extension attempts to aid you in securing your application from such attacks.
This extension is based on the excellent Django middleware.
Installation¶
Install the extension with one of the following commands:
$ easy_install flask-seasurf
or alternatively if you have pip installed:
$ pip install flask-seasurf
Usage¶
Using SeaSurf is fairly straightforward. Begin by importing the extension and then passing your application object back to the extension, like this:
import Flask
from flask_seasurf import SeaSurf
app = Flask(__name__)
csrf = SeaSurf(app)
This extension is configurable via a set of configuration variables which can be added to the Flask app’s config:
Variable name |
Description |
Default |
---|---|---|
CSRF_COOKIE_NAME |
The cookie name, also used as the session variable name. |
‘_csrf_token’ |
CSRF_COOKIE_TIMEOUT |
After what time the cookie expires. |
timedelta(days=5) |
CSRF_COOKIE_SECURE |
Whether the cookie is required to be transferred over a secure connection. |
False |
CSRF_COOKIE_HTTPONLY |
HttpOnly flag of the cookie. Whether it can be read by JS. |
False |
CSRF_COOKIE_PATH |
Indicates a URL path that must exist in the requested URL in order to send the Cookie header |
‘/’ |
CSRF_COOKIE_DOMAIN |
Setting the cookie domain. |
None |
CSRF_COOKIE_SAMESITE |
Setting the cookie SameSite policy. |
‘Lax’ |
CSRF_CHECK_REFERER |
Enable checking the Referer header. |
True |
CSRF_DISABLE |
Disables CSRF protection globally. |
False unless app.config[‘TESTING’] is set to True |
CSRF_HEADER_NAME |
The header that would contain the token. |
‘X-CSRFToken’ |
SEASURF_INCLUDE_OR_EXEMPT_VIEWS |
Possible values: ‘exempt’, ‘include’. |
‘exempt’ |
All the values prefixed with CSRF_COOKIE_ are passed verbatim to the Response.set_cookie method.
Corresponding code will need to be added to the templates where POST, PUT, and DELETE HTTP methods are anticipated. In the case of POST requests a hidden field should be added, something like this:
<form method="POST">
...
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
</form>
The extension adds a global function to the Jinja template engine called csrf_token. This is a function that retrieves the current token and will be matched against the request token.
By default all requests that are not GET, HEAD, OPTIONS, or TRACE are validated against the CSRF token sent by the client and as rendered on the page. However a view may be completely exempted from validation using the exempt decorator. For instance it’s possible to decorate a view as shown below:
@csrf.exempt
@app.route('/exempt_view', methods=['POST'])
def exempt_view():
'''This view is exempted from CSRF validation.'''
return 'foobar'
By default when a request is determined to be secure, i.e. using HTTPS, then we use strict referer checking to prevent a man-in-the-middle attack from being plausible. To disable checking the Referer header, set the Flask app’s config CSRF_CHECK_REFERER to False.
Note
Setting
TESTING = True
in config will disable the token generation!If you are getting
None
as token check ifTESTING = True
in config.
AJAX Usage¶
AJAX is not exempted from CSRF validation as it is a plausible vector for cross-site request forgery. As such, POSTing with AJAX can make use of the aforementioned method, but other HTTP methods, such as PUT and DELETE might be better suited to using the X-CSRFToken header instead.
Essentially this header is passed back to the backend by way of extracting the token from the cookie using JavaScript. For a better explanation of how this might be done please refer to the Django CSRF documentation.
Flask-WTForms Usage¶
If you would like to use Flask-Seasurf with a form generator, such as WTForms, it is possible to do so. Below is a simple example.
First we will define a custom SeaSurfForm object in a seasurf_form module like so:
from wtforms import Form, HiddenField
from flask import g
# import your app here
from your_project import app
class SeaSurfForm(Form):
@staticmethod
@app.before_request
def add_csrf():
csrf_name = app.config.get('CSRF_COOKIE_NAME', '_csrf_token')
setattr(SeaSurfForm,
csrf_name,
HiddenField(default=getattr(g, csrf_name)))
Now assume we define a module forms as such:
from wtforms.validators import DataRequired, StringField, PasswordField, Email
from seasurf_form import SeaSurfForm
class LoginForm(SeaSurfForm):
email = StringField('email', validators=[DataRequired(), Email()])
password = PasswordField('password', validators=[DataRequired()])
This is the basis of our login form which we will serve up in a view to the user. Finally we can use this in our template login.html:
<form method="POST" action="{{ url_for('login') }}">
{{ form.hidden_tag() }}
<p>
{{form.email.label }} {{ form.email(size=50) }}
</p>
<p>
{{form.password.label }} {{ form.password(size=50) }}
</p>
<p>
<input type="submit" value="Login">
</p>
</form>
API¶
- class flask_seasurf.SeaSurf(app=None)¶
Primary class container for CSRF validation logic. The main function of this extension is to generate and validate CSRF tokens. The design and implementation of this extension is influenced by Django’s CSRF middleware.
Tokens are generated using a salted SHA1 hash. The salt is based off a random range. The OS’s SystemRandom is used if available, otherwise the core random.randrange is used.
You might intialize
SeaSurf
something like this:csrf = SeaSurf()
Then pass the application object to be configured:
csrf.init_app(app)
Validation will now be active for all requests whose methods are not GET, HEAD, OPTIONS, or TRACE.
When using other request methods, such as POST for instance, you will need to provide the CSRF token as a parameter. This can be achieved by making use of the Jinja global. In your template:
<form method="POST"> ... <input type="hidden" name="_csrf_token" value="{{ csrf_token() }}"> </form>
This will assign a token to both the session cookie and the rendered HTML which will then be validated on the backend. POST requests missing this field will fail unless the header X-CSRFToken is specified.
Excluding Views From Validation
For views that use methods which may be validated but for which you wish to not run validation on you may make use of the
exempt
decorator to indicate that they should not be checked.- disable_cookie(callback)¶
A decorator to programmatically disable setting the CSRF token cookie on the response. The function will be passed a Flask Response object for the current request.
The decorated function must return
True
orFalse
.Example usage of
disable_cookie
might look something like:csrf = SeaSurf(app) @csrf.disable_cookie def disable_cookie(response): if is_api_request(): return False return True
- exempt(view)¶
A decorator that can be used to exclude a view from CSRF validation.
Example usage of
exempt
might look something like this:csrf = SeaSurf(app) @csrf.exempt @app.route('/some_view') def some_view(): return render_template('some_view.html')
- Parameters
view – The view to be wrapped by the decorator.
- generate_new_token()¶
Delete current CSRF token and generate a new CSRF token. This function should only be called inside a view function to avoid conflicts with other operations that this library performs during the request context
- include(view)¶
A decorator that can be used to include a view in CSRF validation.
Example usage of
include
might look something like this:csrf = SeaSurf(app) @csrf.include @app.route('/some_view') def some_view(): return render_template('some_view.html')
- Parameters
view – The view to be wrapped by the decorator.
- init_app(app)¶
Initializes a Flask object app, binds CSRF validation to app.before_request, and assigns csrf_token as a Jinja global.
- Parameters
app – The Flask application object.
- set_cookie(view)¶
A decorator that can be used to force setting the CSRF token cookie on the request. By default, the CSRF token cookie is set on all requests unless the view is decorated with
exempt
. This decorator is a noop unless used in conjuction withexempt
.Example usage of
set_cookie
might look something like this:csrf = SeaSurf(app) @csrf.exempt @csrf.set_cookie @app.route('/some_view') def some_view(): return render_template('some_view.html')
- Parameters
view – The view to be wrapped by the decorator.
- skip_validation(callback)¶
A decorator to programmatically disable validating the CSRF token cookie on the request. The function will be passed a Flask Request object for the current request.
The decorated function must return
True
orFalse
.Example usage of
skip_validation
might look something like:csrf = SeaSurf(app) @csrf.skip_validation def skip_validation(request): if is_api_request(): return False return True
- validate()¶
Validates a CSRF token for the current request.
If CSRF token is invalid, stops execution and sends a Forbidden error response to the client. Can be used in combination with
exempt
to programmatically enable CSRF protection per request.Example usage of
validate
might look something like:csrf = SeaSurf(app) @csrf.exempt @app.route('/sometimes_requires_csrf') def sometimes_requires_csrf(): if not oauth_request(): # validate csrf unless this is an OAuth request csrf.validate() return render_template('sometimes_requires_csrf.html')