How to Setup User Authentication for Dash Apps using Python and Flask
By: Eric Kleppen
Tags: Programming, Data Science, Security, Analytics, Web Development

Dash Apps

Dash apps are composed of a Layout and Callbacks:

Layout

The layout is made up of a tree of components that describe what the application looks like and how users experience the content. Dash comes with several component libraries out of the box.

Callbacks

Callbacks make the Dash apps interactive. Callbacks are Python functions that are automatically called whenever an input property changes.

Dash App Starter Code

Here is the Dash app starter code for the authentication walk-throughs. The advanced example will use the same dropdown and graph functionality, but add significantly more complexity.

#dependencies

import dash

import dash_core_components as dcc

import dash_html_components as html

import plotly #instantiate Dash

app = dash.Dash(__name__) #Set the layout

app.layout = html.Div([

html.H1('A Simple Dash App')

, dcc.Dropdown(

id='dropdown'

, options=[{'label': i, 'value': i}

for i in ['Day 1', 'Day 2']]

, value='Day 1')

, dcc.Graph(id='graph')

]) #end div

@app.callback(

dash.dependencies.Output('graph', 'figure')

, [dash.dependencies.Input('dropdown', 'value')])

def update_graph(dropdown_value):

if dropdown_value == 'Day 1':

return {'layout': {'title': 'Graph of Day 1'}

, 'data': [{'x': [1, 2, 3, 4]

, 'y': [4, 1, 2, 1]}]}

else:

return {'layout': {'title': 'Graph of Day 2'}

,'data': [{'x': [1, 2, 3, 4]

, 'y': [2, 3, 2, 4]}]} #set the callback for the dropdown interactivitydash.dependencies.Output('graph', 'figure'), [dash.dependencies.Input('dropdown', 'value')])def update_graph(dropdown_value):if dropdown_value == 'Day 1':return {'layout': {'title': 'Graph of Day 1'}, 'data': [{'x': [1, 2, 3, 4], 'y': [4, 1, 2, 1]}]}else:return {'layout': {'title': 'Graph of Day 2'},'data': [{'x': [1, 2, 3, 4], 'y': [2, 3, 2, 4]}]} if __name__ == '__main__':

app.run_server(debug=True)

Notice the app layout consists of three components (html.H1, dcc.Dropdown, dcc.Graph) and a callback to make the it a little interactive. A user can select between two items in the droplist, and the graph will update accordingly.

When the app is run in the terminal, it can be accessed locally through the browser using this URL: http://127.0.0.1:8050/. If it was hosted somewhere, anyone could access it and view the graph. Since I only want certain people to access the dashboard, I’ll use Dash-Auth to set up basic HTTP authentication.

Basic Authentication using Dash-Auth

Using the Dash-Auth library is easy, but it has some limitations. For example, using basic authentication, I must hardcode usernames and passwords in a dictionary and send the user/password pair to the users I want accessing the app. Users are not able to create their own account or change the password, and users cannot log out. In the advanced example using Flask-Login, I’ll let the user create their own username and password!

WARNING: Treat the username/password pairs like API keys and keep them out of the source code repository! Remember to add the file to your .gitignore.

File Structure

The app uses two files. I keep the username/password pairs stored in a python file as text, separate from the application code. In the advanced example, I’ll use an SQLite database to store the user data.

File structure

File users.py

The file users.py contains the username/password dictionary. If I need to let three people access the Dash app, I need to create three username/password pairs like this:

#user dictionary

USERNAME_PASSWORD_PAIRS = {

'user1': 'test1'

, 'user2': 'test2'

, 'user3': 'test3'

}

File app.py

The file app.py contains the code for the Dash app. For the dependencies, import the dash_auth library and the user dictionary from the users.py file.

#import dash-auth and users dictionary

import dash_auth

from users import USERNAME_PASSWORD_PAIRS

Next, add this little bit of boilerplate code after instantiating the app in the app.py file:

auth = dash_auth.BasicAuth(app, USERNAME_PASSWORD_PAIRS)

It should look like this:

#import dependencies

import dash_auth

from users import USERNAME_PASSWORD_PAIRS #instantiate Dash

app = dash.Dash(__name__)

auth = dash_auth.BasicAuth(

app,

USERNAME_PASSWORD_PAIRS

) ### layout and callbacks ###

...

That’s it! Basic authentication is now set up. When opening the Dash app, the user will be prompted for a username and password.

Dash authentication prompt

Enter one of the user/password combinations from the users.py file to gain access to the dashboard.

Behind the scenes, the HTTP authentication framework is challenging the client request, forcing it to provide authentication information. The process beings with the server responding to the client with an Unauthorized response status (401). A client that wants to authenticate with the server must include an Authorization request header with credentials.

Note, the exchange must happen over an HTTPS (TLS) connection to be secure. This process is defined by RFC 7235.

General HTTP authentication framework (Mozilla Contributors) https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication

Advanced Authentication using Flask-Login

The Dash-Auth library provides an easy way for users to access the dashboard, but provides little in the ways of session management. For a more robust authentication solution, I recommend the Flask-Login library. It stores the active users’ ID in the session, allowing them to log in and out while protecting users’ sessions from being stolen by cookie thieves. According to the documentation:

It handles the common tasks of logging in, logging out, and remembering your users’ sessions over extended periods of time.

While Powerful, Flask-Login does have a few limitations. It has no inherent way to register users or recover accounts. I will explain how to create those functions. Additionally, the library only handles logging in or out and no other permissions.

Workflow to Authenticate through the Dash app

Dependencies

This advanced example uses several powerful Python libraries. Explaining them all in-depth is beyond the scope of this tutorial, but I do my best to explain each component at a high level.

Use pip to install these necessary libraries: sqlalchemy, flask-sqlalchemy, flask-login, werkzeug, and configparser.

#import dependencies #manage database and users

import sqlite3

from sqlalchemy import Table, create_engine

from sqlalchemy.sql import select

from flask_sqlalchemy import SQLAlchemy

from flask_login import login_user, logout_user, current_user, LoginManager, UserMixin #manage password hashing

from werkzeug.security import generate_password_hash, check_password_hash #use to config server

import warnings

import configparser

import os #dash dependencies

import dash_core_components as dcc

import dash_html_components as html

import dash

from dash.dependencies import Input, Output, State

File Structure

The file structure is similar to the first example in that it only contains two files. The file app.py contains the code for the Dash application and will be composed of several different layouts to accommodate creating the user account, logging in, and displaying the graphed data.

File structure

File data.sqlite

I recommend creating the database with a Users table before creating the Dash app.

The data.sqlite file is the database and stores the username, password, and email address in the Users table. To protect user passwords, the password will be hashed using the werkzeug library. Werkzeug is an advanced web server gateway interface (WSGI) utilities library.

Setting up the Database and Users Table

If the database does not already exist, it will be created automatically when connecting.

conn = sqlite3.connect('data.sqlite')

As seen in the authentication workflow image above, I want users to create their own accounts. The database connection and insert statement used to write the user account to the database will be managed by the SQLAlchemy library. Additionally, since Dash is built on top of Flask, I’ll use Flask-SQLAlchemy to configure the server to interact with the database.

SQLAlchemy is a powerful and flexible enterprise-level SQL toolkit and Object Relational Mapper for Python. Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to the Flask application. Since Dash is built on Flask, everything works together perfectly.

Create the Users Table

To store user accounts in the database, I’ll need a table with columns for username, password, and email. SQLAlchemy can handle creating the table. Use the following template to create the Users table.

Note, this can be done using raw SQL instead of SQLAlchemy.

conn = sqlite3.connect('data.sqlite') #connect to the database

engine = create_engine('sqlite:///data.sqlite')

db = SQLAlchemy() #class for the table Users

class Users(db.Model):

id = db.Column(db.Integer, primary_key=True)

username = db.Column(db.String(15), unique=True, nullable = False)

email = db.Column(db.String(50), unique=True)

password = db.Column(db.String(80)) Users_tbl = Table('users', Users.metadata) #fuction to create table using Users class

def create_users_table():

Users.metadata.create_all(engine) #create the table

create_users_table()

Notice the table is created by running the create_users_table function.

I find it is easiest to check the table data using the pandas library because dataframes present the data beautifully in columns and rows.

import pandas as pd

c = conn.cursor()

df = pd.read_sql('select * from users', conn)

df

Querying the Users table

Now that the database and table is set up, it is time to create the Dash App!