Flask Authentication with Dash pages

Hi!

I implemented the new dash pages from dash labs, as mentioned here Dash Labs Pages

I have been trying to add Flask authentication alongside it, as mentioned here Flask authentication with Dash

I don’t seem to be able to get it to work. The code from the Flask authentication page seems to conflict with the dash.register_page when calling the layout() function required by dash pages within app.py (as part of a callback to check for flask authentication & dcc.Location (it errors out by mentioning that dash does not have a register_page)

I was wondering if there is a way to integrate Flask authentication with the new dash pages

Thanks!

4 Likes

Hi @MarcooPoloo
Welcome to the community and thank you for your question. Dash pages is still beta version, so to speak, since it is in Dash Labs. That might be the reason.

Can you sure a minimal working example with us, so we can try it out locally?

Hi @adamschroeder ,

Since I’m interested as well in the topic, I have combined the tutorials mentioned above with the new Dash pages of Dash Labs tutorial.

The app consists out of app.py file, where the flask auth is setup. Next, 3 pages: login.py (login page layout & does auth), home.py and historical_analysis.py (example pages of Dash Labs tutorial). This is what the folder looks like:

app.py
.env
/pages
--- home.py
--- login.py
--- historical_analysis.py

I’m running into similar issue as reported by @MarcooPoloo. Whenever you add the Flask auth code, the dash.register_page seems to only find the ‘login.py’ file. The ‘flask_login’ & class ‘User’ imports in login.py seem to interfer with the registering of the pages. The navbar will only show the ‘login’ page and not all three pages (login, home & historical_analysis).

Below you can find the code.

app.py

import dash
import dash_labs as dl
import dash_bootstrap_components as dbc
from dash import dcc, html

from dash.dependencies import Input, Output, State


import flask
import os
from flask_login import login_user, LoginManager, UserMixin, logout_user, current_user


# Exposing the Flask Server to enable configuring it for logging in
server = flask.Flask(__name__)
app = dash.Dash(__name__, server=server,
                title='Example Dash login',
                update_title='Loading...',
                suppress_callback_exceptions=True, plugins=[dl.plugins.pages], external_stylesheets=[dbc.themes.BOOTSTRAP])

# Updating the Flask Server configuration with Secret Key to encrypt the user session cookie
server.config.update(SECRET_KEY=os.getenv('SECRET_KEY'))

# Login manager object will be used to login / logout users
login_manager = LoginManager()
login_manager.init_app(server)
login_manager.login_view = '/login'

# User data model. It has to have at least self.id as a minimum

class User(UserMixin):
    def __init__(self, username):
        self.id = username

@ login_manager.user_loader
def load_user(username):
    ''' This function loads the user by user id. Typically this looks up the user from a user database.
        We won't be registering or looking up users in this example, since we'll just login using LDAP server.
        So we'll simply return a User object with the passed in username.
    '''
    return User(username)

navbar = dbc.NavbarSimple([
        dbc.NavItem(dbc.NavLink(page['name'], href=page['path']))
        for page in dash.page_registry.values()
        if page["module"] != "pages.not_found_404"
], brand='Dash App')

app.layout = dbc.Container(
    [navbar, dl.plugins.page_container, html.Div(id="logincheck_div"), html.Div(id="output_div")],
) 

@app.callback(
    Output("output_div","children"),
    Input("logincheck_div","children"),
)
def check_logged_in(div):
    if current_user.is_authenticated:
        print("User is logged in")
    else:
        print("User is not logged in, you are being redirected.")
        return(dcc.Location(id="redirect",pathname="/login"))

if __name__ == "__main__":
    app.run_server(debug=True)

.env


login.py

import dash
from dash import dcc, html, Input, Output, callback, State
import dash_bootstrap_components as dbc
from flask_login import login_user, LoginManager, UserMixin, logout_user, current_user
from app import User

dash.register_page(__name__, path="/login")

layout = html.Div([
        html.H1('Login page'),
        dbc.Label("Username"),
        dbc.Input(id = "input_username"),
        dbc.Label("Password"),
        dbc.Input(id = "input_password"),
        dbc.Button("Login in", id = "button_login"),
        dbc.Label("Please login", id = "label_feedback"),
        dcc.Location(id="redirect"),
])

@callback(
    Output('redirect', 'pathname'),
    Output('label_feedback', 'children'), 
    Input('button_login', 'n_clicks'), 
    State('input_username', 'value'), 
    State('input_password', 'value')
    )
def login_button_click(n_clicks, username, password):
    if n_clicks != 0:
        if username == 'test2' and password == 'test':
            user = User(username)
            login_user(user)
            return (("/"),'Succesfully logged in!')
        else:
            return (None, 'Incorrect username or password')

Excuse any rookie mistakes. I’m quite new to Dash, and even less to Flask (but here to learn! :smiley: )

Thank you for writing this example up, @F2P. We are aware that this is a challenge right now.

@AnnMarieW is the principle maintainer of Dash pages. She will try to look into this next week.

2 Likes

Hey @MarcooPoloo and @F2P

Thanks for the pointer to the article about using flask-login with Dash - that was a helpful example.

I adapted the code in the article so that it works with the dash-labs pages plug-in . You can find the code here: GitHub - AnnMarieW/dash-flask-login: An example multi-page Dash app with Flask-Login integration

The original example had all the code in one file. I modified it so that all of the flask-login definitions and the callbacks that handle the login and log-out remained in app.py The login status is saved in a dcc.Store so it’s accessible to all the other pages which are in the pages folder. The example demonstrates requiring a login only for page-2

The biggest change is to let pages/ do it’s magic and handle the routing for you. You’ll notice that there is no need for a main router callback, and in fact dcc.Location is not used in any callback Outputs.

I hope this helps. Let me know if you have more questions :slight_smile:

7 Likes

Just got done playing with new feature Pages, it is excellent. Saves tons of time with boilerplate code.

This solution works but would prefer to avoid the round trip callback. Maybe when instantiating Dash object pass use_pages=True and maybe pages_store=‘my-pages-session-store’ and it’s State can be evaluated and passed as a param to the layout() functions without pinging back to client again.

1 Like

Hello, I did everything as it says here GitHub - AnnMarieW/dash-flask-login: An example multi-page Dash app with Flask-Login integration but the first time I run the program I get the following error

NotFound
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

but when the page is refreshed, the error disappears and the application starts working. Stuck on this error for a few days, any advice would be greatly appreciated

@Sohibjon Sorry you were stuck on this for days :slightly_frowning_face:
This is a new bug in Dash 2.6.0 and should be fixed in the next release. You won’t see this error in dash==2.5.1

Also, I made some updates to this app a couple days ago, so please be sure you are using the latest version.

1 Like

hello, I integrated my multipage application as shown in your github, but I encountered an incomprehensible error. With Flask_login , the app works fine, but when I login to the app, when I redirect through the pages, I get the following error [An object was provided as children instead of a component, string, or number (or list of those). Check the children property that looks something like:] and at the same time the page freezes hard and opens pages incorrectly, what do you think could be the error

1 Like

@Sohibjon Could you make a minimal example that reproduces the error?

yes of course, this is a link to my code where you can implement and depict on error Error_Dash_flask_login/Dash_multipage_error at main · s07012001/Error_Dash_flask_login · GitHub

in fact, this is the error that crawls out of every recommendation

I would be very grateful if you could help me with this problem.

1 Like

Thanks for the code example @AnnMarieW – I modified the login callback to use ldap3 for AD authentication (also based on naderelshehabi’s code). I was wondering if you could recommend the best way to introduce redirects within the /pages framework (e.g. upon successful login, the user is directed to the home page, or perhaps an example using the next functionality to send the user where they were trying to go before logging in). Thank you!

1 Like

I’m facing the same issue. Was this resolved?

Hello @rkshyp,

Welcome to the community!

Take a look here and see if this can help you out.

2 Likes

hello, I tried the repo and feel it must be useful, thank you so much.
but I got a erro like this , could you help me :

RuntimeError: The session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret.


Traceback (most recent call last):
File “/mnt/c/Users/crui/Desktop/dash-flask-login-main/app.py”, line 88, in login_button_click
login_user(User(username))
File “/home/cr/.local/lib/python3.8/site-packages/flask_login/utils.py”, line 184, in login_user
session[“_user_id”] = user_id
File “/home/cr/.local/lib/python3.8/site-packages/flask/sessions.py”, line 98, in _fail
raise RuntimeError(
RuntimeError: The session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret.