This template repository contains a Flask app using the Bootstrap UI framework. The app is structured based on best practices and experience gained through previous implementations. By using a template repository you can generate a new repository with the same directory structure and files to get a new project started quicker.
- Python 3.7.x or higher
- Redis 4.0.x or higher (for rate limiting, otherwise in-memory storage is used)
python3 -m venv venv
source venv/bin/activate
pip3 install -r requirements.txt ; pip3 install -r requirements_dev.txt
flask run
Run the test suite
python -m pytest --cov=app --cov-report=term-missing --cov-branch
This template app uses a number of packages to provide the following features with sensible defaults. Please refer to the specific packages documentation for more details.
Custom CSS and JavaScript files are merged and compressed using Flask Assets and Webassets. This takes all *.css
files in app/static/src/css
and all *.js
files in app/static/src/js
and outputs a single compressed file to both app/static/dist/css
and app/static/dist/js
respectively.
CSS is minified using CSSMin and JavaScript is minified using JSMin. This removes all whitespace characters, comments and line breaks to reduce the size of the source code, making its transmission over a network more efficient.
Merged and compressed assets are browser cache busted on update by modifying their URL with their MD5 hash using Flask Assets and Webassets. The MD5 hash is appended to the file name, for example custom-d41d8cd9.css
instead of a query string, to support certain older browsers and proxies that ignore the querystring in their caching behaviour.
Uses Flask WTF and WTForms to define and validate forms. Forms are rendered in your template using regular Jinja syntax with the relevent Bootstrap classes applied:
<form action="" method="post" novalidate>
{{ form.csrf_token }}
<div class="mb-3">
{{ form.email_address.label(class="form-label") }}
{{ form.email_address(class="form-control", type="email", spellcheck="false", autocomplete="email") }}
<div id="emailHelp" class="form-text">{{ form.email_address.description }}</div>
</div>
</form>
Uses Flask WTF to enable Cross Site Request Forgery protection per form and for the whole app.
Uses Flask Talisman to set HTTP headers that can help protect against a few common web application security issues.
- Forces all connects to
https
, unless running with debug enabled. - Enables HTTP Strict Transport Security.
- Sets Flask's session cookie to
secure
, so it will never be set if your application is somehow accessed via a non-secure connection. - Sets Flask's session cookie to
httponly
, preventing JavaScript from being able to access its content. - Sets X-Frame-Options to
SAMEORIGIN
to avoid clickjacking. - Sets X-XSS-Protection to enable a cross site scripting filter for IE and Safari (note Chrome has removed this and Firefox never supported it).
- Sets X-Content-Type-Options to prevent content type sniffing.
- Sets a strict Referrer-Policy of
strict-origin-when-cross-origin
that governs which referrer information should be included with requests made.
A strict default Content Security Policy (CSP) is set using Flask Talisman to mitigate Cross Site Scripting (XSS) and packet sniffing attacks. This prevents loading any resources that are not in the same domain as the application, with the following exceptions:
style-src
andscript-src
can be loaded from the Bootstrap CDN over HTTPS only. These resources also use Subresource Integrity (SRI) to specify a base64-encoded sha384 cryptographic hash for additional security.- To enable Bootstrap components that include embedded SVGs, an additional policy of
img-src data: 'self'
has also been added.
Uses Flask Compress to compress response data. This inspects the Accept-Encoding
request header, compresses using either gzip, deflate or brotli algorithms and sets the Content-Encoding
response header. HTML, CSS, XML, JSON and JavaScript MIME types will all be compressed.
Uses Flask Limiter to set request rate limits on routes. The default rate limit is 2 requests per second and 60 requests per minite (whichever is hit first) based on the clients remote IP address. Every time a request exceeds the rate limit, the view function will not get called and instead a HTTP 429 status will be returned. If you're implementing user authentication using Flask Login you should also use a key_func
to identify users on routes that require authentication, for example:
@login_required
@limiter.limit("2 per second", key_func=lambda: current_user.id)
This fixes the issue of rate limiting multiple users behind a single IP NAT or proxy, since the request is identified using a different unique value for each user.
Rate limit storage can be backed by Redis using the RATELIMIT_STORAGE_URL
config value in config.py
, or fall back to in-memory if not present. Rate limit information will also be added to various response headers.