How Protect Dash Routes With Login After Dash 1.0 Release

So where’s the Dash 1.0 release thread? The part about making dash work like other flask extensions excites me more than anything. Anyway,…

Before Dash 1.0 release, I had a function like below that I would call after creating dash app and setting a flask app to it as the server.

def protect_views(app):
    for view_func in app.server.view_functions:
        if view_func.startswith(app.url_base_pathname):
            app.server.view_functions[view_func] = login_required(app.server.view_functions[view_func])

    return app

With the Dash 1.0 update, there’s a nice and consistent way to declare a dash app without a server and then using the the init_app(app) function to pass a standard flask application to it. My question is, if I’m also using flask-login, how do I protect the routes of the dash app after calling the init_app to give dash a server? My setup with the factory pattern with Blueprints is shown below.

I have an application_folder which will contain my flask app and its routes, etc. application_folder has a folder called dash_application. dash_application folder contains dash_example.py as below:

##########################
import glob
from pathlib import Path
from dash import Dash
import dash_table
import dash_html_components as html
import dash_core_components as dcc
import pandas as pd
from .layout import html_layout
from flask_login import login_required


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

    # Create Dash Graph
    dash_app.layout = html.Div([
    html.H1('Dash application'),
    dcc.Graph(
        id='basic-graph',
        figure={
            'data':[
                {
                    'x': [0, 1],
                    'y': [0, 1],
                    'type': 'line'
                }
            ],
            'layout': {
                'title': 'Basic Graph'
                }
                }
            )
        ])

    return dash_app
##########################

The application_folder has my init file as:

#############################################
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, login_required
from .dash_application import dash_example


## Globally accessible libraries
db = SQLAlchemy()
login_manager = LoginManager()
login_manager.login_view = 'auth_bp.login'
dash = dash_example.dash_application()



def create_app():
    # Initialize the core application.
    app = Flask(__name__, instance_relative_config=False)
    app.config.from_object('config.Config')

    # Initialize Plugins
    db.init_app(app)
    login_manager.init_app(app)
    
    # This is where a dash app attaches to a server
    dash.init_app(app=app)

    with app.app_context():
        # Set global variables


        # Include our Routes
        from . import routes
        from . import auth


        # Register Blueprints
        app.register_blueprint(auth.auth_bp)
        app.register_blueprint(routes.main_bp)

        db.create_all()

        return app
############################################

application_folder also has routes.py

###############################
import os
from flask import Blueprint, render_template, redirect, url_for
from flask_login import current_user, login_required


main_bp = Blueprint('main_bp', __name__,
                    template_folder='templates',
                    static_folder='static')


@main_bp.route('/')
@login_required
def home():
    return render_template("index.html")


@main_bp.route('/page2')
@login_required
def home2():
    return "This is page 2"
####################################

application_folder also has auth.py

###################################
import os
from flask import Blueprint, render_template, redirect, url_for, request
from flask_login import current_user, login_required, login_user, logout_user
from werkzeug.urls import url_parse


from .models import User
from .forms import LoginForm
from . import db


auth_bp = Blueprint('auth_bp', __name__,
                    template_folder='templates',
                    static_folder='static')


@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('main_bp.home'))
    
    form = LDAPLoginForm()
    if form.validate_on_submit():
        login_user(user=form.user)
        next_page = request.args.get('next')
        if not next_page or url_parse(next_page).netloc != '':
            next_page = url_for('main_bp.home')
        return redirect(next_page)

    return render_template('login.html', title='Sign In', form=form)

@auth_bp.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for("auth_bp.login"))
##########################################

The route for the dash app (/dashapp/) as specified in the dash_example.py file is currently not protected from unauthorized user sessions. I need to protect that route like I have done for ‘/’ and ‘/page2’.

Before the dash 1.0 update, I could call the protect_views function and pass the dash application to it. Since it had a server at that point, the function would protect that route. Now, with the update, the dash app doesn’t get a server until the init_app is called on it. This happens in my init file as part of the factory pattern. After dash gets a flask server, I do not see the url_base_pathname attribute of the dash instance anymore. So that function doesn’t work anymore.

I simply want to be able to protect the view functions on the dash app itself. Any help would be appreciated.

I get this error:

AttributeError: 'Dash' object has no attribute 'url_base_pathname'

I do not understand how I was able to set url_base_pathname when declaring the dash app but then it does not have this attribute. Did something change in dash 1.0 with this?

Yes - Dash 1.0 moved nearly all the constructor kwargs into app.config rather than direct attributes of the app.

We also restrict some of them to be read-only - including url_base_pathname, because changing it after construction wouldn’t have the same effect as setting a different value in the constructor. Might be able to relax that restriction if needed, for now it was just reflecting the existing situation.

1 Like

Thanks so much! Next time, I have to read the docs before asking questions.

three reasons why you should buy plotly pro: support open source, get great support, host your plots and dashboards online