How to improve the security of your Flask application

Web applications face many security issues that can be exploited and lead to loss of data, data theft or the injection and execution of malicious code. This post will give an overview about two Flask packages, Flask-Talisman and Flask-Seasurf, to improve the security of a Flask application.

Flask-Talisman

Flask-Talisman provides HTTP security headers to protect against multiple web application security issues: It enables HTTP Strict Transport Security, helps to avoid clickjacking, prevents content type sniffing, sets the session cookie to secure, forces connects to https and sets an extremely strict content security policy by default. This setting should be changed, however, because it only allows loading of resources that are in the same domain as the application. Additionally, all scripts need to have a nonce to be allowed to run. Scripts without a nonce will not be executed and a console error will be logged.

Installation and import

Install with your preferred package manager, e.g. pipenv install flask-talisman

Add it to your imports: from flask_talisman import Talisman

Add nonces to your scripts

To allow scripts to be executed, a nonce has to be added to the script tag in the corresponding template. Below are two examples, one for jQuery, and one for the main script of the app.

<script nonce="{{ csp_nonce() }}" src="https://code.jquery.com/jquery-3.6.0.min.js"
       integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous" defer></script>
<script nonce="{{ csp_nonce() }}" src="{{ url_for('static', filename='js/script.js') }}" defer></script>

The CSP directive to which the nonce-source should be added needs to be definec explicitly in the content security policy. For the above examples, this means adding 'self', and '*.jquery.com' as sources to the csp. An example of a content security policy will be given shortly.

Add Talisman with custom csp and default options

# add HTTP security headers and nonce
Talisman(app, content_security_policy=csp,
         content_security_policy_nonce_in=['script-src', 'script-src-elem'])

The available options for Flask-Talisman are set to secure settings by default, so that they don't have to changed in most use cases. You can find the details about these settings here.

Content security policy example

The example below shows a content security policy that allows loading resources from the same origin as the app. The same origin includes the scheme (http:// or https://) as well as the domain name.. Images sources are allowed from any domain. Additionally, resources from fontawesome.com, herokuapp.com, jsdelivr.net, and jquery.com are allowed.

# whitelist domains for content security policy
csp = {
    'base-uri': [
        ' \'self\'',
    ],
    'default-src': [
        ' \'self\'',
        '*.fontawesome.com',
        '*.herokuapp.com',
        '*.jsdelivr.net'
    ],
    'img-src': '*',
    'script-src': [
        ' \'self\'',
        '*.fontawesome.com',
        '*.herokuapp.com',
        '*.jquery.com',
        '*.jsdelivr.net'
    ],
    'script-src-elem': [
        ' \'self\'',
        '*.fontawesome.com',
        '*.herokuapp.com',
        '*.jquery.com',
        '*.jsdelivr.net'
    ]
}

For more information about content security policy and more examples, please refer to this MDN article.

Flask-Seasurf

Flask-Seasurf protects against cross-site request forgery. Requests using POST, PUT, and DELETE HTTP methods will need to provide a matching CSRF token to be determined as secure and result in a server response.

Installation, import, and usage

Install with your preferred package manager, e.g. pipenv install flask-seasurf

Add it to your imports: from flask_seasurf import Seasurf

Pass the application object back to Seasurf:

app = Flask(__name__)
csrf = SeaSurf(app)

The CSRF token has to be added to templates that use POST, PUT, and DELETE HTTP methods. Below is an example for a login form:

 <form class="form" method="POST" action="{{ url_for('login') }}">
                <input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">

For requests that are not POST, the hidden field is not required. It is possible to skip the CSRF validation. This has to be done in the view by decorating it with the @csrf.exempt decorator.

For more information and use cases, please refer to the Flask-Seasurf docs.

Avatar for Scott Böning

Written by Scott Böning

Software engineer using mostly Python with a passion for coffee and cats.

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.