Deployment

Any WSGI server runs a flask-peewee app. With the tutorial’s app.py:

$ pip install gunicorn
$ gunicorn -b 127.0.0.1:8000 app:app
$ pip install waitress
$ waitress-serve --listen=127.0.0.1:8000 app:app
$ pip install uwsgi
$ uwsgi --http 127.0.0.1:8000 --master --die-on-term --module app:app

--die-on-term makes SIGTERM stop uwsgi instead of reloading it.

The REST api’s basic-auth backends read the Authorization header. If API writes start returning 401 behind a new server or proxy, check that the header survives the hop.

Behind a proxy

When nginx or another proxy fronts the app, trust its forwarded headers so redirects and generated urls come out right, including when the app is mounted under a path prefix:

from werkzeug.middleware.proxy_fix import ProxyFix

app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_prefix=1)

Sessions

Login state is stored in the flask session. Use a long random SECRET_KEY, and on HTTPS set the cookie flags:

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'

Auth.login_user() marks the session permanent, so PERMANENT_SESSION_LIFETIME bounds how long a login lasts (flask defaults it to 31 days). See also clear_session in Authentication.

CSRF protection

The form templates, the login page included, carry a hidden _csrf_token input wired for flask-seasurf:

from flask_seasurf import SeaSurf

csrf = SeaSurf(app)
csrf.exempt_urls(('/api',))

With SeaSurf initialized, a form post without a valid token is rejected with a 403. The REST api authenticates by header rather than by session, so exempt its prefix as above, or every API write is rejected too.

Static files

The admin’s stylesheets and scripts are served by its blueprint, which any WSGI server handles. At admin traffic levels that is fine. Alias /admin/static/ from the web server if you want it off the app.