For a simple web application in a home automation scenario, basic authentication can be a sufficient solution. Setting up a REST API and a web app with Flask is very easy, and adding basic authentication requires just a few more steps that can be reused between different applications.
Let’s say we have a Flask application that defines routes for a web page (index.html) and a REST endpoint that returns a secret message in JSON-format:
To protect these resources with basic authentication we want to check if the client is already authorized and in that case return the normal responses with status code 200. If the client is not authorized, a response with code 401 (unauthorized) that states that basic authentication is needed should be returned. When a browser receives this information, it will bring up a login dialog. Logging in will create a new request with an Authentication header containing the user name and password.
To achieve this with Python and Flask, we can use the wrap decorator in the functools library to create an authorization decorator that can be used on any function. I’ve take this idea from Armin Ronacher’s snippet http://flask.pocoo.org/snippets/8/
If we decorate a function with @requires_authorization, the authorization check will run first, and if already authorized, the function will be executed. If not authorized, the authentication will be issued. In my example we would have:
Note that the @requires_authorization decorator must be placed after Flask’s @app.route decorator.
Adding SSL
As the user name and password is sent in clear text with every request when using basic authentication it is possible for a hacker to sniff the credentials. To add one degree of protection we can use encryption with https/SSL. To do this, we first need to create an ssl key and a certificate. OpenSSL can be used for this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
openssl genrsa 1024 > ssl.key | |
openssl req -new -x509 -nodes -sha1 -days 365 -key ssl.key > ssl.cert |
Then, instead of starting the flask app with just app.run(host=’0.0.0.0′) we provide an extra argument that is a tuple with the paths to the key and cert files.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
context = ('ssl.cert', 'ssl.key') | |
app.run(host='0.0.0.0', port=80, ssl_context=context) |
Parsing arguments
When the application is started, I want to provide a user and and password for the one and only user that is allowed to access the system. I use the getopt module for this and store the data in Flask’s config dictionary for later use:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys, getopt | |
from basic_auth import app | |
def main(argv): | |
user = '' | |
password = '' | |
try: | |
opts, args = getopt.getopt(argv,"u:p:") | |
except getopt.GetoptError: | |
printHelp() | |
sys.exit(2) | |
for opt, arg in opts: | |
if opt in ("-u"): | |
user = arg | |
elif opt in ("-p"): | |
password = arg | |
if len(user) == 0 or len (password) == 0: | |
printHelp() | |
sys.exit(0) | |
print('User name is:', user) | |
print('Password is:', password) | |
app.config['USERNAME']=user | |
app.config['PASSWORD']=password | |
def printHelp(): | |
print('Usage: ',__file__,'-u <user name> -p <password>') | |
if __name__ == '__main__': | |
main(sys.argv[1:]) | |
context = ('ssl.cert', 'ssl.key') | |
app.run(host='0.0.0.0', port=80, ssl_context=context) |
Now I can start the Flask application with a specified user and password that must be provided for accessing the REST API and the web page.
Structuring the application
A complete example can be fetched from GitHub:
https://github.com/LarsBergqvist/python_flask_authentication
It is setup as a structured Flask application where a runserver.py file is used for parsing the input and starting the program. __init__.py in a subfolder contains the creation of the Flask app object and the routes and helper function are put in separate files/modules in the same subfolder. If you want to try this example, make sure to put the generated ssl key and cert in the same folder as the runserver.py script. The application can be started with e.g.:
sudo python3 runserver.py -u JeffLynne -p ELO
and you should then be able to browse to https://localhost:80 and login with your specified user to see the message. You can also browse to https://localhost:80/api/secret to see the secret in JSON format via the api.