My dash app uses Google OAuth for authentication. Currently I have the user’s Access Token set to expire after 1 hour. I would like to throw a pop up stating that the user has been logged out and to redirect them to the sign in page with a button in the pop up to sign back in.
I am trying to use dcc.Interval but the problem is that it seems to stop when the browser tab is not in focus. Is there a workaround in Flask, Dash or JavaScript to either keep the dcc.Interval running when not in focus or to redirect the user to /logout after an hour?
Does anyone know how long dcc.Interval will run while not in focus? I am also thinking of maybe just writing the JS to keep track of the last user activity and after 10 minutes of inactivity redirect the user to /logout
Basically, on a db you keep track of the last callback executed by the user, could listen on the server using the flask.before_request method to do that.
Then have a function that checks logged in users and their last activity time, if it is outside of the time frame, then you send a message and when the message is received it causes them to log out.
This might be easier to perform on something like a quartz server too, but a little more tricky to have the socket have the user info, since that info is in a different server.
I was able to solve the issue by changing the dcc.Interval trigger time. I originally had it set to trigger once after one hour, but now I have it set trigger every 5 minutes and it is working. I also ended up saving the users lastLogin timestamp in a db and in a dcc.Store so I am using the actual login timestamp and not the page load.
def homepage_layout():
layout = html.Div([
# logout popup
dcc.Interval(id='timeout-interval', disabled=False, interval=300000), # 5 min
dcc.Store(id='login-time'),
dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle('Logged Out'), close_button=False),
dbc.ModalBody('Users are automatically logged out after one hour. Click the sign in button '
'below to log back into the dashboard.'),
dbc.ModalFooter(dcc.Link(dbc.Button('Sign In'), href='/logout', refresh=True))
],
id='timeout-modal',
fullscreen=False,
size='l',
keyboard=False,
backdrop='static',
centered=True,
backdrop_class_name ='timeout-backdrop',
),
# the other .py files under /pages will render here
page_container
])
return layout
app.layout = homepage_layout
@app.callback(Output('timeout-modal', 'is_open'),
Output('timeout-interval', 'disabled'),
Input('timeout-interval', 'n_intervals'),
State('login-time', 'data'),
prevent_initial_call=True)
def timeout(_, login_time):
"""Show a popup when the user's access token expires. The token expires after 60 minutes"""
now = pd.to_datetime(datetime.datetime.utcnow(), utc=True).value
# convert nanoseconds to seconds
if (now - pd.to_datetime(login_time, utc=True).value) * 10**-9 > 3600: # 60 minutes
return True, True
return no_update, no_update
As long as it is working for you, this however will not log them out on the server.
For example, a user would still have access to all callbacks in the above. Driving this from the server would make it so that these sessions are no longer active.
@jinnyzor Good point, thank you. I handle the authentication, session and token in my auth file. The code above just strictly throws a popup on the page after an hour, which is when their token expires.
Have you considered a clientside callback for this. I work with dashboards that make the user refresh their token every 30 minutes, and I have not been facing issues with the interval component not being active. In my dashboard the interval component is linked to a clientside callback.
Great that it worked! Just to answer your previous question, yes I poll once every 30 minutes. But in my use case, I set max_intervals=-1 as the refresh should never stop as long as the tab is open.