`_, so
this behavior was changed and :func:`~flask.jsonify` now supports serializing
arrays.
+
+Security Headers
+----------------
+
+Browsers recognize various response headers in order to control security. We
+recommend reviewing each of the headers below for use in your application.
+The `Flask-Talisman`_ extension can be used to manage HTTPS and the security
+headers for you.
+
+.. _Flask-Talisman: https://github.com/GoogleCloudPlatform/flask-talisman
+
+HTTP Strict Transport Security (HSTS)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Tells the browser to convert all HTTP requests to HTTPS, preventing
+man-in-the-middle (MITM) attacks. ::
+
+ response.haders['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
+
+- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
+
+Content Security Policy (CSP)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Tell the browser where it can load various types of resource from. This header
+should be used whenever possible, but requires some work to define the correct
+policy for your site. A very strict policy would be::
+
+ response.headers['Content-Security-Policy'] = "default-src: 'self'"
+
+- https://csp.withgoogle.com/docs/index.html
+- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
+
+X-Content-Type-Options
+~~~~~~~~~~~~~~~~~~~~~~
+
+Forces the browser to honor the response content type instead of trying to
+detect it, which can be abused to generate a cross-site scripting (XSS)
+attack. ::
+
+ response.headers['X-Content-Type-Options'] = 'nosniff'
+
+- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
+
+X-Frame-Options
+~~~~~~~~~~~~~~~
+
+Prevents external sites from embedding your site in an ``iframe``. This
+prevents a class of attacks where clicks in the outer frame can be translated
+invisibly to clicks on your page's elements. This is also known as
+"clickjacking". ::
+
+ response.headers['X-Frame-Options'] = 'SAMEORIGIN'
+
+- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+
+X-XSS-Protection
+~~~~~~~~~~~~~~~~
+
+The browser will try to prevent reflected XSS attacks by not loading the page
+if the request contains something that looks like JavaScript and the response
+contains the same data. ::
+
+ response.headers['X-XSS-Protection'] = '1; mode=block'
+
+- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
+
+Set-Cookie options
+~~~~~~~~~~~~~~~~~~
+
+These options can be added to a ``Set-Cookie`` header to improve their
+security. Flask has configuration options to set these on the session cookie.
+They can be set on other cookies too.
+
+- ``Secure`` limits cookies to HTTPS traffic only.
+- ``HttpOnly`` protects the contents of cookies from being read with
+ JavaScript.
+- ``SameSite`` ensures that cookies can only be requested from the same
+ domain that created them. It is not supported by Flask yet.
+
+::
+
+ app.config.update(
+ SESSION_COOKIE_SECURE=True,
+ SESSION_COOKIE_HTTPONLY=True,
+ )
+
+ response.set_cookie('username', 'flask', secure=True, httponly=True)
+
+- https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies
+
+HTTP Public Key Pinning (HPKP)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This tells the browser to authenticate with the server using only the specific
+certificate key to prevent MITM attacks.
+
+.. warning::
+ Be careful when enabling this, as it is very difficult to undo if you set up
+ or upgrade your key incorrectly.
+
+- https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning
diff --git a/examples/flaskr/README b/examples/flaskr/README
index 90860ff2..f60287fa 100644
--- a/examples/flaskr/README
+++ b/examples/flaskr/README
@@ -9,9 +9,11 @@
~ How do I use it?
- 1. edit the configuration in the flaskr.py file or
+ 1. edit the configuration in the factory.py file or
export an FLASKR_SETTINGS environment variable
- pointing to a configuration file.
+ pointing to a configuration file or pass in a
+ dictionary with config values using the create_app
+ function.
2. install the app from the root of the project directory
@@ -19,7 +21,7 @@
3. Instruct flask to use the right application
- export FLASK_APP=flaskr
+ export FLASK_APP=flaskr.factory:create_app()
4. initialize the database with this command:
diff --git a/examples/flaskr/flaskr/__init__.py b/examples/flaskr/flaskr/__init__.py
index d096b1e7..e69de29b 100644
--- a/examples/flaskr/flaskr/__init__.py
+++ b/examples/flaskr/flaskr/__init__.py
@@ -1 +0,0 @@
-from .flaskr import app
diff --git a/examples/flaskr/flaskr/blueprints/__init__.py b/examples/flaskr/flaskr/blueprints/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/examples/flaskr/flaskr/flaskr.py b/examples/flaskr/flaskr/blueprints/flaskr.py
similarity index 55%
rename from examples/flaskr/flaskr/flaskr.py
rename to examples/flaskr/flaskr/blueprints/flaskr.py
index b4c1d6bd..7b64dd9e 100644
--- a/examples/flaskr/flaskr/flaskr.py
+++ b/examples/flaskr/flaskr/blueprints/flaskr.py
@@ -10,29 +10,18 @@
:license: BSD, see LICENSE for more details.
"""
-import os
from sqlite3 import dbapi2 as sqlite3
-from flask import Flask, request, session, g, redirect, url_for, abort, \
- render_template, flash
+from flask import Blueprint, request, session, g, redirect, url_for, abort, \
+ render_template, flash, current_app
-# create our little application :)
-app = Flask(__name__)
-
-# Load default config and override config from an environment variable
-app.config.update(dict(
- DATABASE=os.path.join(app.root_path, 'flaskr.db'),
- DEBUG=True,
- SECRET_KEY='development key',
- USERNAME='admin',
- PASSWORD='default'
-))
-app.config.from_envvar('FLASKR_SETTINGS', silent=True)
+# create our blueprint :)
+bp = Blueprint('flaskr', __name__)
def connect_db():
"""Connects to the specific database."""
- rv = sqlite3.connect(app.config['DATABASE'])
+ rv = sqlite3.connect(current_app.config['DATABASE'])
rv.row_factory = sqlite3.Row
return rv
@@ -40,18 +29,11 @@ def connect_db():
def init_db():
"""Initializes the database."""
db = get_db()
- with app.open_resource('schema.sql', mode='r') as f:
+ with current_app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
-@app.cli.command('initdb')
-def initdb_command():
- """Creates the database tables."""
- init_db()
- print('Initialized the database.')
-
-
def get_db():
"""Opens a new database connection if there is none yet for the
current application context.
@@ -61,14 +43,7 @@ def get_db():
return g.sqlite_db
-@app.teardown_appcontext
-def close_db(error):
- """Closes the database again at the end of the request."""
- if hasattr(g, 'sqlite_db'):
- g.sqlite_db.close()
-
-
-@app.route('/')
+@bp.route('/')
def show_entries():
db = get_db()
cur = db.execute('select title, text from entries order by id desc')
@@ -76,7 +51,7 @@ def show_entries():
return render_template('show_entries.html', entries=entries)
-@app.route('/add', methods=['POST'])
+@bp.route('/add', methods=['POST'])
def add_entry():
if not session.get('logged_in'):
abort(401)
@@ -85,26 +60,26 @@ def add_entry():
[request.form['title'], request.form['text']])
db.commit()
flash('New entry was successfully posted')
- return redirect(url_for('show_entries'))
+ return redirect(url_for('flaskr.show_entries'))
-@app.route('/login', methods=['GET', 'POST'])
+@bp.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
- if request.form['username'] != app.config['USERNAME']:
+ if request.form['username'] != current_app.config['USERNAME']:
error = 'Invalid username'
- elif request.form['password'] != app.config['PASSWORD']:
+ elif request.form['password'] != current_app.config['PASSWORD']:
error = 'Invalid password'
else:
session['logged_in'] = True
flash('You were logged in')
- return redirect(url_for('show_entries'))
+ return redirect(url_for('flaskr.show_entries'))
return render_template('login.html', error=error)
-@app.route('/logout')
+@bp.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were logged out')
- return redirect(url_for('show_entries'))
+ return redirect(url_for('flaskr.show_entries'))
diff --git a/examples/flaskr/flaskr/factory.py b/examples/flaskr/flaskr/factory.py
new file mode 100644
index 00000000..07de0aa6
--- /dev/null
+++ b/examples/flaskr/flaskr/factory.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+"""
+ Flaskr
+ ~~~~~~
+
+ A microblog example application written as Flask tutorial with
+ Flask and sqlite3.
+
+ :copyright: (c) 2015 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+import os
+from flask import Flask, g
+from werkzeug.utils import find_modules, import_string
+from flaskr.blueprints.flaskr import init_db
+
+
+def create_app(config=None):
+ app = Flask('flaskr')
+
+ app.config.update(dict(
+ DATABASE=os.path.join(app.root_path, 'flaskr.db'),
+ DEBUG=True,
+ SECRET_KEY='development key',
+ USERNAME='admin',
+ PASSWORD='default'
+ ))
+ app.config.update(config or {})
+ app.config.from_envvar('FLASKR_SETTINGS', silent=True)
+
+ register_blueprints(app)
+ register_cli(app)
+ register_teardowns(app)
+
+ return app
+
+
+def register_blueprints(app):
+ """Register all blueprint modules
+
+ Reference: Armin Ronacher, "Flask for Fun and for Profit" PyBay 2016.
+ """
+ for name in find_modules('flaskr.blueprints'):
+ mod = import_string(name)
+ if hasattr(mod, 'bp'):
+ app.register_blueprint(mod.bp)
+ return None
+
+
+def register_cli(app):
+ @app.cli.command('initdb')
+ def initdb_command():
+ """Creates the database tables."""
+ init_db()
+ print('Initialized the database.')
+
+
+def register_teardowns(app):
+ @app.teardown_appcontext
+ def close_db(error):
+ """Closes the database again at the end of the request."""
+ if hasattr(g, 'sqlite_db'):
+ g.sqlite_db.close()
diff --git a/examples/flaskr/flaskr/templates/layout.html b/examples/flaskr/flaskr/templates/layout.html
index 737b51b2..862a9f4a 100644
--- a/examples/flaskr/flaskr/templates/layout.html
+++ b/examples/flaskr/flaskr/templates/layout.html
@@ -5,9 +5,9 @@
Flaskr
{% for message in get_flashed_messages() %}
diff --git a/examples/flaskr/flaskr/templates/login.html b/examples/flaskr/flaskr/templates/login.html
index ed09aeba..505d2f66 100644
--- a/examples/flaskr/flaskr/templates/login.html
+++ b/examples/flaskr/flaskr/templates/login.html
@@ -2,7 +2,7 @@
{% block body %}
Login
{% if error %}Error: {{ error }}{% endif %}
-