Embed Multiple Dash Apps in Flask with Microsoft Authentication

Phew! A journey (by examples) to get all the parts to come together to build a scalable template for multiple Dash apps deployed together with unified navigation and authentication

Steve Kiefer
Towards Data Science

--

Image by Alexas_Fotos from Pixabay

Full disclosure: I am not an expert in developing web applications. I don’t even consider myself a data scientist. Since I found Dash I have been on a journey to build a system that works for my use-case. This includes trying to glue together multiple loosely related groups of Dash apps developed using a windows computer but deployed to an Ubuntu VM behind a company’s firewall. Add in some authentication that users wont hate (i.e. something they already use) and I have arrived at something that I couldn’t find a clean example of so I thought I’d share my current solution and its evolution.

I am a structural analyst and I use Python daily to process test data, scale or combine analysis results, run optimizations or trade studies, and/or develop methods of analysis specific to a component or mechanism. Most other engineers do not use python. So once I have developed or coded up an existing analysis method, I usually want to deploy it to others in a controlled way, with a familiar and user-friendly interface…enter Plotly’s Dash framework!

Plotly’s Dash Framework

I am a huge fan of Plotly’s Dash framework. Making a Dash app is pretty simple if you are already familiar with python. Add in Dash Bootstrap Components and smooth, good-looking, and responsive web apps are just a few extra lines of code away. Going from existing Python scripts to web app is a matter of 1–2–3

  1. Creating the layout with components for user inputs (buttons, dropdowns, number/text input, etc.) and regions with results (plots, tables, etc.) using layout elements (div, row, column, etc.)
  2. Linking up the user inputs to the functions that actually do the work with callbacks
  3. Deploy the app or just run it on a host machine or VM accessible to your intended users
Plotly’s Dash, image source: https://plotly.com/dash/

Dash apps look pretty, you can code them in Python (avoiding javascript and raw html), and can have a wide audience using the same ‘controlled’ tool (no passing around excel files thank you very much). Check out the Dash documentation if you aren’t familiar with it. There are a lot of other excellent tutorials on building Dash apps by people that are undoubtedly better than me so I’ll keep this section short. The Dash community forum and stack overflow are your friends.

Ok so now we have a sweet way to get a pretty tool out to the team. But I have lots of tools, some related, others not. If we could only group these apps together somehow…

Embed Multiple Dash Apps in a Flask App

This seems to be a fairly common request. Plotly offers Dash Enterprise to help deploy multiple apps to a larger audience while maintaining security. This could be an easier more scalable solution for your team especially if you have multiple developers. For those of us where Dash Enterprise is not an option, the Plotly team discusses several methods to create multi-page apps in the open-source documentation. The first is to create multi-page dash apps by sending different layouts to the view using dcc.Location & dcc.Link components interacting through a callback. This worked ok for me for a few small apps, but as the number of apps increased it seemed to get messy. I prefer to have separate apps be stand-alone objects. I usually start development by creating a stand-alone Dash app locally before integrating into the rest of the apps for deployment. After an app has been deployed I’ll often go back and update it. Once it was integrated (at least the way that I implemented it) I had to load the entire app system just to test one portion. This got old pretty quick. So I started looking for a different way.

At some point in my journey, the Plotly team added a discussion on deploying Dash apps in existing web apps to the documentation. I also came across this excellent article:

At the time I had not worked with Flask at all so templates, views, blueprints, databases were all new to me. It took some time to absorb it all (at least enough to try and work with it). The Flask Mega Tutorial helped a ton. With Oleg’s Medium article and the Flask Mega Tutorial (plus a lot of google -> stackoverflow) I started to piece together a new (to me) way to package my Dash Apps by embedding them into a ‘parent’ Flask app.

A Basic Example

Most of the examples I came across were basically just toys. I didn’t feel the ones I came across covered enough of a template to make it scalable. I think my biggest issue was how to integrate a unified navigation and Bootstrap formatting in the Flask part. The example here starts the skeleton that I will build on in following sections. The fully functioning project covered in this section can be found on github under the ‘basic’ branch.

Lets start with the project directory structure:

dash_in_flask_msal
│ config.py
│ env_p38_dash.yml
│ secret.env

├───app
│ __init__.py

├───dashapps
│ │ dash_app_1.py
│ │ dash_app_2.py
│ │ routes.py
│ │ __init__.py
│ │
│ ├───templates
│ └───dashapps
│ dash_app.html
│ dash_app_debug.html
|
├───main
│ routes.py
│ __init__.py

├───static
├───templates
base.html
index.html

I am using blueprints (‘main’ & ‘dashapps’) and an application factory. The main blueprint doesn’t really do anything right now, it just serves the index. There are 2 Dash apps in the ‘dashapps’ blueprint (dash_app_1.py & dash_app_2.py). This is a fairly standard Flask app layout.

The Application Factory

The create_app() application factory lives in the __init__.py file directly under the app directory. It is fairly basic at this point. We import the Config object from the config.py file (which is basically empty) and attach it to the app. We initialize the bootstrap-flask extension, and with an app_context register the blueprints. That is all normal Flask. Now comes the new stuff: we import a add_dash function from each of the Dash app files and pass the Flask app through each one.

Application factory for basic dash in Flask

Lets take a look at one of the Dash app files. The dash_app_1.py file looks pretty normal. The main differences are:

  1. The creation of the Dash app and all callbacks are inside of a add_dash() function which takes the Flask app (server) and uses it when creating the Dash app object and after creating all the callbacks returns the Flask app
  2. We pass a custom url_base_pathname parameter when creating the Dash app via the URL_BASE global variable
  3. We use a global variable APP_ID as a prefix to all the component ids.
Typical Dash app wrapped in a ‘add_dash’ function

Ok so now we see how the Dash apps are attached to the mother Flask object but they still look very familiar. To complete the setup we need view functions to expose the Dash app. Lets take a look at the routes.py file in the dashapps blueprint folder.

Routes.py in the dashapps blueprint

One thing to notice is that the url_base_pathname used to create the app is not the url used for the view function. The Dash app endpoint is at /dash/dash_app_1 but the view function route is /dash_app_1. To see what is going on we will have to take a look at the template.

Templates & Bootstrap!

There is a base template that all other templates inherent (base.html). This is where we will setup the navigation system. In order to use Bootstrap styling in the Flask bits I found the Bootstrap-Flask library. Not to be confused with Flask-Bootstrap (which doesn’t support Bootstrap 4). I really wanted to avoid HTML so at one point I tried Flask-Nav which seemed promising, only later to find that Flask-Bootstrap was looking like it was going to stay at Bootstrap 3. So Bootstrap-Flask and a little HTML and off we go! I started with the Bootstrap-Flask Starter Template (of course!). I then went to the Bootstrap 4.0 website and grabbed an example navbar section that included drop down menus and plopped it in a jinja2 block named ‘navbar’ which was placed in a container. I added a messages section for sending messages with categories just blow the navbar. You can see the dropdown links are created by url_for(‘dashapps.dash_app_1’) where dash_app_1 is the view function for that app. There is also a {% content %} block that all the other templates will be working in.

Interesting navigation bits of the base.html template

Now we see the difference in url_base_pathname in the dash app and the endpoint of the view function. The method I’m using for embedding Dash apps into Flask is to run the Dash app on its own url and then inject it into the template via an iFrame element. In the view function that calls the dash_app.html template I pass the Dash app url (dash_url) and a min_height argument to define the height of the iFrame (I couldn’t find a way for the iFrame to be responsive).

dash_app.html template

Lets see it work!

Unified navigation with Bootstrap 4!

Now for a bonus! At the end of the Dash app file is the following code. This allows the dash app to be run standalone but inside a template (dash_app_debug.html) which is very similar to the main template.

This allows for standalone Dash app testing (no need to fire up the whole enchilada!

Now with Persistent Data (i.e. a Database)!

Without persistent data, apps are pretty straightforward. Many of the apps I build are just fancy calculators or data parsers so they don’t need a database. However some do. As a structural analysist I live and breathe material properties, coupon test data, vibration test data. Sure we could copy excel or csv files to the static or asset directory and load them up form there, but that is a pretty fragile way to store data. Ideally we would have something multiple users can read and write to in a safe way…enter the database! I’m using SQLite because I don’t need much else for my use case (I’m not re-creating Instagram here) but I think it could be adapted to another database type without too much trouble. I am also using the Flask-Sqlalchemy extension. The fully functioning project covered in this section can be found on GitHub under the ‘now_with_db’ branch. This example adds a Dash app that users can upload images with some data to and another app that displays the images and data.

Mind blowing persistent data!

Lets review the new directory structure! I trimmed some of the contents that haven’t changed from the ‘basic’ example from the directory structure below. Now we can see a db folder that lives next to the app folder. It has my_data.db & users.db files as well an img sub directory with some *.jpg files.

dash_in_flask_msal           
├───app
│ │ __init__.py
│ │
│ ├───dashapps
│ │ │ dash_app_1.py
│ │ │ dash_app_2.py
│ │ │ models.py
│ │ │ routes.py
│ │ │ user_image_upload.py
│ │ │ user_image_view.py
│ │ │ __init__.py
│ │ │
│ │ ├───templates
│ │
│ ├───main
│ │
│ ├───static
│ ├───templates

├───db
│ my_data.db
│ users.db

└───img
649303dc7de4402fb62acbd33a163e37.jpg
ca49fc3d1e944398b42a95b04db14366.jpg
d6f47fcc13e2408a88b512729b280b09.jpg

The config file in the basic example wasn’t much, just the ‘Brand’ for the navbar. We will now need to add some more configuration variables. We are adding the usual SQLALCHEMY_DATABASE_URI as an SQLite database with a default users.db filename and SQLALCHEMY_BINDS with my_data bind attached to a my_data.db sqlite file. We aren’t using the users.db database yet so its technically not needed now (we will get there!). We have a custom config parameter IMG_FILE_DIR which defaults to the img directory below the new db folder.

We have also added 2 new Dash apps (user_image_view.py & user_image_upload.py) and a new models.py file. Before we dive in to the new database usage lets take a look at the routes.py file in the dashapps blueprint directory:

We added 2 new routes (/image_view & /image_upload). The idea for all of this is to try and keep the focus on the individual Dash apps and not spend too much time integrating (once the skeleton is setup). In addition to the routes.py file additions, I also added a new dropdown group in the base.html template for navigation. Adding additional apps is looking pretty simple so far…

Lets now check out the database model for the new Dash apps:

Here we create a User_Image model with some string data columns and a LargeBinary type. Lets take a look at the user_image_view.py app for how these get implemented:

We import the db from app and the User_Image model from app.dashapps.models. We have the usual global variables and everything is inside an add_dash function. Whats new is that after creating the Dash app we create the layout as a function serve_layout() instead of a static layout variable. What this allows is that on every page load this function is called and we can read the database. Look Ma! No callbacks! Right at the top of the function we query the database for all of the User_Image objects with:

uimgs = db.session.query(User_Image).all()

and when creating the layout loop through the uimgs and populate the layout with the data in Dash Bootstrap Components Cards. Notice the thumb property of the User_Image object (LargeBinary) is actually an image stored directly in the database and the data gets attached to the src property. Storing images directly in the database is not ideal but storing large blobs can be useful.

The user_image_upload.py Dash app is more of a traditional Dash app with a static layout. There is a lot more going on in there so check it out on Github. The basic idea is that there are a few input fields and an upload component as well as a Card component. We are basically building the Card for the user_image_view.py app. In addition there is a datatable for loading existing image data so they can be modified later. Here we add an image:

Populating the data and adding an image to the database

There are a few tricks in the file including using dash.callback_context to determine which component triggered the callback (mainly so 1 feedback div can be updated by multiple processes). Below is a snippet in the ‘save’ callback where a new image is added to the database. If a row is selected derived_virtual_selected_rows will be a list of length 1 and will contain the primary key database id for the object. Its retrieved and the contents are overwritten with data in the app fields.

Ok so we can add items to a database and view them! But we can’t let everyone add their favorite cat photos, that would just melt the server. So we need a way to limit access to the upload….

Adding Microsoft Authentication Library (MSAL)

You don’t need another password to try and remember, I know I don’t. Ideally any password we add would be one the target audience is already using. I work in a Microsoft world where a lot of people spend a lot of time using Microsoft products.….enter Microsoft Authentication Library (MSAL).

The Microsoft identity platform is a OAuth 2.0 and OpenID Connect standard-compliant authentication service. You can follow Microsoft’s quickstart to add authentication to a python web app. At first it was confusing for me (remember I am not a web developer). This example helped a lot more than the Microsoft documentation. Another blog post by Miguel Grinberg about OAth authentication in Flask also influenced my learning. Miguel’s OAuth post may help if you would like to swap out Microsoft Authentication for Twitter or Facebook OAuth authentication.

The basic steps are:

  1. Register your app and callback in Azure (follow the quickstart)
  2. Collect your Tenant ID, Client ID, and Client Secret and get them into your Flask config
  3. Add some functions that generate the url to get authentication and a way to catch the return

The ‘Azure-Samples’ example didn’t store users in a database. I wanted to use Flask-login so I could simply decorate callbacks with @login_required for portions of the site that would require credentials, so I mixed the examples and hacked away until something worked. An almost-functional example is posted on Github under the ‘msal_login’ branch. To elevate it to fully functional you need to supply the TENANT_ID, CLIENT_ID, CLIENT_SECRET.

Lets start off with project directory:

dash_in_flask_msal
│ config.py
│ env_p38_dash.yml
│ secret.env

├───app
│ │ __init__.py
│ │
│ ├───auth
│ │ │ models.py
│ │ │ routes.py
│ │ │ __init__.py
│ │ │
│ │ └───templates
│ │ └───auth
│ │ login.html
│ │
│ ├───dashapps
│ │ │ dash_app_1.py
│ │ │ dash_app_2.py
│ │ │ models.py
│ │ │ routes.py
│ │ │ user_image_upload.py
│ │ │ user_image_view.py
│ │ │ __init__.py
│ │ │
│ │ ├───templates
│ │ └───dashapps
│ │ dash_app.html
│ │ dash_app_debug.html
│ │
│ ├───main
│ │ routes.py
│ │ __init__.py
│ │
│ ├───static
│ ├───templates
│ base.html
│ index.html

├───db
│ my_data.db
│ users.db

└───img
649303dc7de4402fb62acbd33a163e37.jpg
770a75b7e77f4914b85eb610582b3cb3.jpg
ca49fc3d1e944398b42a95b04db14366.jpg
d6f47fcc13e2408a88b512729b280b09.jpg

Most of the files are the same from the last example. however we now have a auth blueprint complete with models, routes, and templates. There is also a secret.env file shown but its not on github. The missing pieces to get this example to work are defined in this file. You can add them directly to your config though.

A totally fake secret.env file… but it contains the variable names!

Before we get into the auth blueprint, lets take a look at how the application factory function has changed.

We see that we are importing the LoginManager from flask-login and we initialize it. We also register the new auth blueprint with a /auth prefix. we also see a new parameter in the add_dash functions: login_req which takes a boolean. We will come back to that.

Now lets take a look at the auth blueprint. In the models.py file you will find a very basic user model that is missing password and login info (because MSAL will take care of that). We just have name and email columns (other than the unique id). The interesting bits are in the blueprint __init__.py file and in the routes.py file.

The __init__.py file has 2 functions (_build_msal_app & _build_auth_url) that we will import and use in the routes.py file. These functions are basically copied from the Azure-Samples Flask example. In the routes.py file I am also sticking pretty close to the Azure-Samples example. The main difference is I am using the familiar @login_required and after authentication is complete either add the user to the database (if its their first time logging in) or log them in via the flask-login.login_user() function. In addition to the auth blueprint there is also some new features in the dashapps blueprint.

dashpps blueprint __init__.py, now with a new function!

We now have this _protect_dashviews() function that iterates through a Dash app’s server’s view function and if it starts with the url_base_pathname we wrap it with login_required(). This function gets imported into each Dash app *.py file and if the new login_req argument of the add_dash()function is True then the Dash app gets passed into _protect_dashviews()before the server is returned during the creation of the application. All credit goes to Oleg as this methodology is from his medium article (linked above), I just reconfigured it as an option instead of applying it to all Dash views.

In addition to protecting the dash views that get pushed to the iFrame, we also have to protect the main view function for that Dash app with the usual @login_required decorator. In this example I am protecting one of the Dash examples and the user_image_upload Dash app.

protected view functions for 2 of the Dash apps
Now we are authenticating!

The End….

Well that was quite the journey. It is not a perfect solution and I am sure there are some holes here and there but I hope this walk through helps you on your next project. Thanks for taking the time to read this!

--

--