Secure flask app with self signed SSL certificate (flask HTTPS)

Here we will secure our flask web application with self signed SSL certificates so we can access the web page via HTTPS. Since the certificates will be self signed, we will not use the chain of trust and our website will still look suspicious to users, they will need to accept security risk in their web browser. Sot this approach is just for testing purposes, do not protect your website this way, it would look more suspicious than when using plain unsecured http.

prerequisites:

$ python3 -m venv flaskssl_env
$ source flaskssl_env/bin/activate
(flaskssl_env)$ pip3 install pyopenssl

Running HTTPS with adhoc SSL (super easy setup):

app.py:

from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/")
def main_page():
    return jsonify({"secured": "Hello world"})

if __name__ == "__main__":
    app.run(debug=True, ssl_context='adhoc')

start the app with:

(flaskssl_env) $ python3 app.py 
 * Serving Flask app 'app' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on https://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 189-994-478

Note: do not start app with flask run, it would ignore our starting arguments and it would start http only.

(flaskssl_env) $ flask run
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Adhoc self signed certificates in flask have one great disadvantage, once we reload our flask server (when debug=True, it will be upon each saved code change), our certificates get regenerated and web browser will prompt user to accept the risk once more. Obviously not ideal solution even for users that in general are willing to trust your self signed certs. For browser to remember our certificates, we would need to have self signed key pair (ideally with very long expiration time).

SSL via self signed key pair:

generate the key pair with openssl utility:

(flaskssl_env) $ which openssl
/usr/bin/openssl
(flaskssl_env) $ openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem keyout priv_key.pem -days 3650

We have created keypair valid for 10 years. cert.pem is the certificate file and priv_key.pem is corresponding private key.

During creation it will ask you several questions, I have filled only value for the hostname, in our case localhost.

(flaskssl_env) $ openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout priv_key.pem -days 3650
Generating a RSA private key
.......++++
.................................................++++
writing new private key to 'priv_key.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:.
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:.
Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
Organizational Unit Name (eg, section) []:.
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:.
(flaskssl_env) $

Keypair will be generated in current directory:

(flaskssl_env) $ ls
app_keypair.py  app.py  cert.pem  flaskssl_env  priv_key.pem  __pycache__

app_keypair.py:
has instead of adhoc statement the actual cert and key pair

from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/")
def main_page():
    return jsonify({"secured": "Hello world"})

if __name__ == "__main__":
    app.run(debug=True, ssl_context=('cert.pem', 'priv_key.pem'))

start the same way as before:

(flaskssl_env) $ python3 app_keypair.py
 * Serving Flask app 'app_keypair' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on https://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 189-994-478

The great benefit with keypair approach is that upon reloading flask backend the web browser remembers the keypair. So user needs to accept the certificates only once (per lifetime of the certificates, until they expire).

Sources: