Been playing around with making authentication smooth on multi-page apps.
Created dash-auth-flow as an example of a smooth authentication flow, but it still had the problem of loading some content before triggering a callback that then checked authentication; each additional function had to then verify authentication. This could easy result in information getting exposed.
So I tried making layouts into conditional functions: if the user is authenticated, return the sensitive layout, otherwise return something else e.g. a dcc.Location(pathname='/login',refresh=True)
which takes the user to a login page. This works!
You can show part of the app and not another, e.g. homepage but not user profile. You can even define all the callbacks you need as long as the part of the page you show doesnāt trigger any callbacks that reference non-existent component IDs.
I will use this in flask_login
e.g. if current_user.is_authenticated...
.
MWE:
test/
pages/
profile.py
app.py
server.py
server.py
import dash
app = dash.Dash(__name__)
app.config.suppress_callback_exceptions = True
app.py
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
from pages import profile
from server import app
app.layout = html.Div([
dcc.Location(id='base-url',refresh=True,pathname='/'),
html.Button('click this',id='button',n_clicks=0),
# links to pages
html.Div([dcc.Link('profile',href='/profile'),html.Br(),dcc.Link('base',href='/'),html.Br()]),
html.Div(id='page-content')
])
# show how many times the button has been clicked
@app.callback(
Output('button','children'),
[Input('button','n_clicks')])
def show_clicks(n):
return 'click this button, clicked {} times'.format(str(n))
# router function
@app.callback(
Output('page-content','children'),
[Input('base-url','pathname')],
[State('button','n_clicks')])
def router(pathname,n):
if pathname=='/':
return 'Base content'
elif pathname=='/profile':
return profile.profile_layout(n)
return '404'
if __name__=='__main__':
app.run_server(debug=True)
pages/profile.py
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Output,Input,State
from server import app
# example of a layout function - usually imported from other
# only show the profile layout if the button has been clicked more than two times
# otherwise, send back to /base
def profile_layout(n):
if n>2:
return [dcc.Location(id='profile-url',refresh=True,pathname='/profile'),
html.Div(id='profile-picture')]
else:
return [dcc.Location(id='profile-url',refresh=True,pathname='/')]
# profile callback
@app.callback(
Output('profile-picture','children'),
[Input('profile-picture','style')])
def return_profile_picture(s):
return ['that worked!',html.Img(src='https://picsum.photos/500/500')]
Tried clicking āProfileā without any button clicks:
Clicked āProfileā with more button clicks:
Has anyone done anything like this?