I came up with a functional, but perhaps suboptimal, solution similar to what I posited in the original post. In the main app.py script, I created an initial callback that checks dcc.Store for the existence/validity of the azure access token. Depending on that condition, a modal is displayed either with authentication instructions or information regarding the validity and duration of the token in local storage. In addition, each page layout is wrapped in a callback function that returns either a default blank layout or the desired layout depending on existence of the access token. These functions also import the data for the page layout.
Keeping the structure of the project consistent with the best practices, the solution would look something like this.
app.py
# imports
# ...
app = Dash(__name__, external_stylesheets=[dbc.themes.CYBORG], use_pages=True)
app.layout = dbc.Container(
[
navbar,
modal_01,
modal_02,
page_container,
dcc.Store(
id="authentication_store_token",
storage_type="local"
)
],
fluid=True,
style={"padding": 0},
className="dbc"
)
# modal callbacks to relay authentication instructions to user...
@callback(
Output("authentication_modal_01", "is_open"),
Output("authentication_modal_01_body", "children"),
State("authentication_store_token", "data"),
State("authentication_modal_01", "is_open"),
Input("authentication_modal_01_button", "n_clicks")
)
def toggle_authentication_modal_01(stored_token_data, opened, n_clicks):
# desired logic/workflow, update number of modals and Inputs/Outputs as needed
# e.g.
if ((token is None) | expired) & (not opened):
#... get device creds (see authentication script in previous post) ...
# open modal, return authentication instructions
return True, return_message
elif: # etc...
if __name__ == "__main__":
app.run(debug=True)
somepage.py
# imports
# ...
# initialize variable
df_stuff = None
# page layout with no children/content
register_page(__name__)
layout = dbc.Container(
id="stuff_layout",
fluid=True,
className="dbc"
)
# callback to render layout depending on existence/validity of token
@callback(
Output("stuff_layout", "children"),
Input("authentication_store_token", "data")
)
def update_page_layout(stored_token_data):
global df_stuff
# The function here is a custom function to parse the data and determine whether token is valid
token, token_expiration, token_expiration_datetime, expired = parse_token(stored_token_data)
# if no token or token is expired, return default layout
if (token is None) or expired:
children = [html.P("Need to Authenticate")]
return children
# otherwise, display page and import data (if not already done)
else:
if df_stuff is None:
# will need to zero pad token first, but then can use token to establish database
# connection
engine = create_engine(
conn_url,
connect_args={"attrs_before": {SQL_COPT_SS_ACCESS_TOKEN: token}}
)
# import data
with engine.connect() as connection:
query_stuff = text(r"""
SELECT *
FROM [DATABASE].[dbo].[TABLE_NAME]
""")
df_stuff = pd.read_sql_query(query_stuff, connection)
# return page layout
return [
# enter desired page layout with components that depend on DataFrame values....
]
# regular component callbacks...
# ...
If anyone has come up with something different, I’d be interested to see the approach.