Here is a login flow that is outside of dash:
"""
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, url_for, render_template
from flask_login import login_user, LoginManager, UserMixin, logout_user, current_user
import dash
from dash import dcc, html, Input, Output, State, ALL
# 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 request.path in ['/login', '/logout']:
return
if current_user:
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(url_for('login'))
else:
if current_user:
if request.path == '/login' or current_user.is_authenticated:
return
return jsonify({'status':'401', 'statusText':'unauthorized access'})
@server.route('/login', methods=['POST', 'GET'])
def login(message=""):
if request.method == 'POST':
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
message = "invalid username and/or password"
else:
if current_user:
if current_user.is_authenticated:
return redirect('/')
return render_template('login.html', message=message)
@server.route('/logout', methods=['GET'])
def logout():
if current_user:
if current_user.is_authenticated:
logout_user()
return render_template('login.html', message="you have been logged out")
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(
[
html.A('logout', href='../logout'),
html.Br(),
dash.page_container,
]
)
if __name__ == "__main__":
app.run_server(debug=True)
You’ll need to create a new templates
folder just like the pages
.
templates/login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flask Login Flow</title>
</head>
<body>
<form method="POST" action="/login">
<div>Please login to continue:</div>
<div><span style="color:red">{{message}}</span></div>
<input placeholder="Enter your username" type="text" id="uname-box" name='username'>
<input placeholder="Enter your password" type="password" id="pwd-box" name='password'>
<Button type="submit" id="login-button">Login</Button>
</form>
</body>
</html>
This is a basic demonstration. If you try to navigate to a page without being logged in, you will be redirected to the login page, and your url saved.
If you are an authenticated user and try to navigate to login, you will be redirected to the home page.
Another benefit of this flow, is that it significantly reduces the complexity of the before_request that determines if it should cater a response.
Some things to always keep in mind:
- flask routes take priority over dash routes.
- As you make new paths available in the Flask side, you will need to make sure you add them as available in the before_request if they are not login required.
- If you want to reference your assets/static folders, you will also need to allow those paths through.