Here, give this a test, it seems to work for me:
"""
CREDIT: This code was originally adapted for Pages based on Nader Elshehabi's article:
https://dev.to/naderelshehabi/securing-plotly-dash-using-flask-login-4ia2
https://github.com/naderelshehabi/dash-flask-login
This version is updated by Dash community member @jinnyzor For more info see:
https://community.plotly.com/t/dash-app-pages-with-flask-login-flow-using-flask/69507
For other Authentication options see:
Dash Enterprise: https://dash.plotly.com/authentication#dash-enterprise-auth
Dash Basic Auth: https://dash.plotly.com/authentication#basic-auth
"""
import os
from flask import Flask, request, redirect, session, jsonify
from flask_login import login_user, LoginManager, UserMixin, logout_user, current_user
import dash
from dash import dcc, html, Input, Output, State, ALL
from dash.exceptions import PreventUpdate
from utils.login_handler import restricted_page
# Exposing the Flask Server to enable configuring it for logging in
server = Flask(__name__)
@server.before_request
def check_login():
if request.method == 'GET':
if current_user:
if request.url in ['http://127.0.0.1:8050/login', 'http://127.0.0.1:8050/logout']:
return
if 'Referer' in request.headers:
if request.headers['Referer'] in ['http://127.0.0.1:8050/login', 'http://127.0.0.1:8050/logout'] and \
request.path in ['/_dash-layout', '/_dash-dependencies']:
return
if current_user.is_authenticated:
return
else:
for pg in dash.page_registry:
if request.path == dash.page_registry[pg]['path']:
session['url'] = request.url
return redirect('http://127.0.0.1:8050/login')
else:
if current_user:
if current_user.is_authenticated or request.path == '/login':
return
if (request.headers['Referer'] in ['http://127.0.0.1:8050/login', 'http://127.0.0.1:8050/logout']
and (request.path in ['/_dash-layout', '/_dash-dependencies'] or
(request.json['changedPropIds'] == ["_pages_location.pathname", "_pages_location.search"]
or request.json['changedPropIds'] == ['{"index":"redirectLogin","type":"redirect"}.n_intervals']))):
return
return jsonify({'status':'401', 'statusText':'unauthorized access'})
@server.route('/login', methods=['POST'])
def login_button_click():
if request.form:
username = request.form['username']
password = request.form['password']
if VALID_USERNAME_PASSWORD.get(username) is None:
return """invalid username and/or password <a href='/login'>login here</a>"""
if VALID_USERNAME_PASSWORD.get(username) == password:
login_user(User(username))
if 'url' in session:
if session['url']:
url = session['url']
session['url'] = None
return redirect(url) ## redirect to target url
return redirect('/') ## redirect to home
return """invalid username and/or password <a href='/login'>login here</a>"""
app = dash.Dash(
__name__, server=server, use_pages=True, suppress_callback_exceptions=True
)
# Keep this out of source code repository - save in a file or a database
# passwords should be encrypted
VALID_USERNAME_PASSWORD = {"test": "test", "hello": "world"}
# 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"
class User(UserMixin):
# User data model. It has to have at least self.id as a minimum
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)
app.layout = html.Div(
[
# dcc.Location(id="url"),
html.Div(id="user-status-header"),
html.Hr(),
dash.page_container,
]
)
# @app.callback(
# Output("user-status-header", "children"),
# Output('url','pathname'),
# Input("url", "pathname"),
# Input({'index': ALL, 'type':'redirect'}, 'n_intervals')
# )
# def update_authentication_status(path, n):
# ### logout redirect
# if n:
# if not n[0]:
# return '', dash.no_update
# else:
# return '', '/login'
#
# ### test if user is logged in
# if current_user.is_authenticated:
# if path == '/login':
# return dcc.Link("logout", href="/logout"), '/'
# return dcc.Link("logout", href="/logout"), dash.no_update
# else:
# ### if page is restricted, redirect to login and save path
# if path in restricted_page:
# session['url'] = path
# return dcc.Link("login", href="/login"), '/login'
#
# ### if path not login and logout display login link
# if current_user and path not in ['/login', '/logout']:
# return dcc.Link("login", href="/login"), dash.no_update
#
# ### if path login and logout hide links
# if path in ['/login', '/logout']:
# return '', dash.no_update
if __name__ == "__main__":
app.run_server(debug=True)
note this removes the location ‘url’ to work, and the logout will not redirect to login, unless you were to add a clientside callback, which might be the way to go.