Forcing Dash App to be called through Flask Route?

Hello All,

I have been attempting to place a Dash App inside Flask. While I have achieved this using the recommended methods in the docs, I am really only integrating Dash into a Flask app to utilize Okta Cloud Security. I have basically followed this link to a T to see if this is even possible.

The only issue I am having is that Dash seems to be a master controller. If “url_base_pathname” or “routes_pathname_prefix” is used, it ignored any flask.route rules for that specific path. Vise versa, if neither of these are used the server defaults to just the Dash APP. Given that the template for okta integrating into flask requires a standard flask.route routing, is it possible to call the Dash App only within the flask.route function?

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_table

from flask import Flask, render_template, g, redirect, url_for
from flask_oidc import OpenIDConnect
from okta import UsersClient

app = Flask(__name__)
app.config["OIDC_CLIENT_SECRETS"] = "client_secrets.json"
app.config["OIDC_COOKIE_SECURE"] = False
app.config["OIDC_CALLBACK_ROUTE"] = "/oidc/callback"
app.config["OIDC_SCOPES"] = ["openid", "email", "profile"]
app.config["SECRET_KEY"] = "{{ LONG_RANDOM_STRING }}"
app.config["OIDC_ID_TOKEN_COOKIE_NAME"] = "oidc_token"
oidc = OpenIDConnect(app)
okta_client = UsersClient("{{ OKTA_ORG_URL }}", "{{ OKTA_AUTH_TOKEN }}")


dash_app = dash.Dash(
    __name__,
    server=app,
    url_base_pathname='/dummypath/'
)

@app.before_request
def before_request():
    if oidc.user_loggedin:
        g.user = okta_client.get_user(oidc.user_getfield("sub"))
    else:
        g.user = None


@app.route("/")
def index():
    return render_template("index.html")


@app.route("/dashboard")
@oidc.require_login
def dashboard():
    #this is where there needs to be a callback which pushes the app through to the /dashboard extension
    return render_template("dashboard.html", dash_url = dash_app.index)


@app.route("/login")
@oidc.require_login
def login():
    return redirect(url_for(".dashboard"))


@app.route("/logout")
def logout():
    oidc.logout()
    return redirect(url_for(".index"))

dash_app.layout = ...

...

if __name__ == '__main__':
    app.run(debug=True)

You may get something useful from this. With dash 1.X, dash can work like any standard flask extension. This is especially helpful with the factory pattern with blueprints.

Thanks for the reply. I have been playing with this a bit the last day and I am not seeing it work at all. Not sure what I am missing.

I have added all of the Dash code into a function:

def dash_page():
    # Create a Dash app
    dash_app = Dash(__name__, 
                    server=False, # Don't give dash a server just yet.
                    url_base_pathname='/dashapp/')

    ....
return dash_app

Then call it in the flask initiation file:

import dash_app
app = flask.Flask(__name__)
dash = dash_app.dash_page()
@app.route("/dashboard")
@oidc.require_login
def dashboard():
     return dash.init_app(app=app)

In this case, the page comes back with

 Not Found

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

Having tried a few other setups and variations, I am not finding a method to perform that late pass of the server to the function (like adding a param to dash_page method, ect.)

So I am not sure what I am missing here.

No one has an idea how to give this another go?

Hey @WolVes, is there a reason for adding the flask server to the dash app inside the dashboard route? That is not how flask works. You’ll need to initialize the flask app and attach it to the dash app before the routes will work.

Your dashboard function is returning an initialized dash object. That cannot render the dashboard.

I’m working on the same issue. Okta with dash. Having similar problems. I’ll post here if I figure it out.

I was able to do this using dash-okta-auth. See my reply to this post https://community.plotly.com/t/enable-okta-login-for-dash/29245/4?u=ghavranek

Hey, I could get this to work for me like this

Should work for any dash app

# -*- coding: utf-8 -*-
"""
Created on Wed Apr 14 11:46:49 2021

@author: DM_wo
"""
import dash
# import dashboard
from flask import Flask, session
from functools import wraps



def logout():
    print("killing session")

def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
            
        user = dict(session).get('user', None)

        if (user is None):
            return logout()  
            
        

        return f(*args, **kwargs)

    return decorated_function


flask_app = Flask(__name__)


dash_base_path='/dash/'
BS = "https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/yeti/bootstrap.min.css"

dash_app = dash.Dash(server=flask_app, url_base_pathname=dash_base_path, external_stylesheets=[BS])
# dash_app.layout = dashboard.create_layout(dash_app)
# dashboard.create_callbacks(dash_app)


for view_func in flask_app.view_functions:
    if view_func.startswith(dash_base_path):
        flask_app.view_functions[view_func] = login_required(flask_app.view_functions[view_func])
    
2 Likes

The following works for me to login-protect all Dash app views using flask-login, so you could do something similar.

Very similar to what @MendesDd did above.

from dash import Dash
from flask_login import login_required


def protect_dashviews(dashapp):
    """If you want your Dash app to require a login,
    call this function with the Dash app you want to protect"""

    for view_func in dashapp.server.view_functions:
        if view_func.startswith(dashapp.config.url_base_pathname):
            dashapp.server.view_functions[view_func] = login_required(
                dashapp.server.view_functions[view_func]
            )

dashapp = Dash()
protect_dashviews(dashapp)
1 Like