dcc.Interval update when not in focus

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

Hello @PyGuy,

You may be able to use something like this:

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.

Thank you @Tobs , I looked into it and a clientside_callback seems like the way to go.

clientside_callback(
    """
        function(_, login_time) {
            const sec_passed = (Date.now() - Date.parse(login_time)) / 1000;
            if (sec_passed >= 3600) {
                return [true, true];
            } else {
                return [false, false];
            };
        }
    """,
    Output('timeout-modal', 'is_open'),
    Output('timeout-interval', 'disabled'),
    Input('timeout-interval', 'n_intervals'),
    State('login-time', 'data'),
    prevent_initial_call=True
)

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.