Authentification using JavaScript and API calls with Dash

Hi everyone,

I am currently trying to implement an authentication service in my Dashboard and I am stuck on a weird error.
To do the authentication, I included my JavaScript file in the asset folder and it works fine when I launch the app / browse between pages, but as soon as I stay on the page for longer, I face the following error:

request[method](url, config.fetch, body)
                   Promise {<rejected>: TypeError: Failed to execute 'fetch' on 'Window': The global scope is shutting down.
    at Object.ā€¦}

from the api.js file.
To give more context, the app is defined as:

app = dash.Dash(__name__,
                external_stylesheets=[STYLE_SHEET_BS_THEME],
                )

print("running from", os.getcwd())

app.config.suppress_callback_exceptions = True
app.scripts.config.serve_locally = True # serve the necessary JavaScript files from the local assets folder.
dcc_date_day_d= dcc.Store(id="date_day_d", data=dt.today().date())
server = s.server


def layout():
    return html.Div(
        [
            setattr(dcc_date_day_d, "data", dt.today().date()),
            dcc.Location(id="url", refresh=True),
            navbar.body,
            html.Div(id="page-content"),
        ],
    )


app.layout = layout()

with the navbar defined in a .py file as:

class NavBar:
    """
    Navigation Bar to change page
    """

    def __init__(self):
        """"""
        self.body = dbc.NavbarSimple([
            self.future_basis,
            self.index,
            self.stock,
            self.database,
        ],
            brand=str_brand,
            brand_href="/home",
            sticky="top",
            color="primary",
            className="navbar navbar-expand-lg navbar-dark bg-primary",
            id="navbar",
        )

A huge thanks to anyone who would try to help, I saw a few times this issue on StackOverflow but it never got answered.

Hello @amgd,

Welcome to the community!

I dont see anything alarming about your setupā€¦ As this is a very limited view of what is going on.

I dont know how your app is doing the authentication, etc or how you render your layouts, or what request is being dropped.

Hi @jinnyzor ,

Thanks for your answer, I will try to provide as much context as I can given that I am really stuck on this and might have missed something.

The way the authentification is done is through this JavaScript function:

console.log("Setting up the properties of the window object")
window.TOKEN_INFO = {};
window.MODE = "PROD";
window.official_end_point = 'my_official_end_point;
window.uat_end_point = 'my_uat_end_point';
window.CONNECT_URL = ((MODE=='PROD')? official_end_point: uat_end_point)

function CONNECT_configure(){
  window.Connect = setupConnect({
      authorization_endpoint: CONNECT_URL,
      client_id: 'my_client_id',
      redirect_uri: 'my_redirect_url',
      scope: 'openid profile'
    });
}

function CONNECT_manage_connect(){
      console.log("Configurating Connect");
      CONNECT_configure();
      console.log("Configuration done.");
      Connect.on('renewAuthorizationSuccess', reloadTokenInfo);      
      if (window.Connect.isAuthorized()){
        console.log("Access authorized .")
        reloadTokenInfo();
        } 
      else if (window.Connect.getAuthorizationError()) {
            var alertStr = 'Authorization error: ' + Connect.getAuthorizationError();
            console.log(alertStr);
              } 
      else {
            console.log("Requesting authorization.")
            window.Connect.requestAuthorization();
            console.log("Reloading the token info after requesting authorization.")
            reloadTokenInfo();
        }
}

function reloadTokenInfo(){
  console.log(
    "window.Connect.getAuthorizationHeader() ",
    window.Connect.getAuthorizationHeader() 
  )
}

CONNECT_manage_connect();

It interacts with a file I load in the external_scripts parameter of my app, which I tried to put in my assets folder to this if this was the cause of my issue.

When I launch the app it works fine and as long I donā€™t spend too much time on one page too, but then it freezes and the app keeps on waiting.
The logs in the browser console are as follow:

Setting up the properties of the window object
Configurating Connect
Configuration done
Access authorized
window.Connect.getAuthorizationHeader() Bearer
 .... [my autorization Header]
SSO extension [my_redirect_url]
page loaded
starting
(after some time -> )  fetch error in the file api.js from dash library

From the web debugger it seems that the issue come from the ā€˜_dash-dependencies urlā€™, but I have no idea how to solve this ā€¦ :confused:

Hmmm.

Are you making sure that you arenā€™t hijacking some of the same objects that Dash uses?

When defining a JS function to just the window, you donā€™t need to define window.

What you could do is maybe define this all in like an Auth namespace. Something like:

authFunc = window.auth = window.auth || {}

Then define your functions inside that dictionary.

You can check out an example of using the namespace from AG Grid. We use this to define custom functions. :grin:

If this doesnā€™t work, how quick is your token expiring? Does it have to interact with get requests? Do you try to reroute all requests?

Thank you very much for your suggestions, I implemented the following in my assets folder:

console.log("Setting up the properties of the window object.")
window.CONNECT_TOKEN_INFO = {};
window.CONNECT_MODE = "PROD";
window.official_end_point = 'my_official_end_point';
window.uat_end_point = 'my_uat_end_point';
window.CONNECT_URL = ((CONNECT_MODE=='PROD')? official_end_point: uat_end_point)
console.log("All the properties of the window have been set.")

window.auth = window.auth || {};

window.auth.CONNECT_configure = function(){
  window.Connect = setupConnect({
      authorization_endpoint: window.CONNECT_URL,
      client_id: 'my_client_id',
      redirect_uri: 'my_redirect_url',
      scope: 'openid profile'
    });
}

window.auth.CONNECT_manage_connect = function(){
      console.log("Configurating Connect");
      window.auth.CONNECT_configure();
      console.log("Configuration of Connect done.");
      window.sgwtConnect.on('renewAuthorizationSuccess', window.auth.reloadTokenInfo);      
      if (window.Connect.isAuthorized()){
        console.log("Access authorized via Connect.")
        window.auth.reloadTokenInfo();
        } 
      else if (window.Connect.getAuthorizationError()) {
            var alertStr = 'Authorization error: ' + Connect.getAuthorizationError();
            console.log(alertStr);
              } 
      else {
            console.log("Requesting authorization.")
            window.Connect.requestAuthorization();
            console.log("Authorization accepeted.");
      }
}

window.auth.reloadTokenInfo = function(){
  console.log(
    "window.Connect.getAuthorizationHeader() ",
    window.Connect.getAuthorizationHeader() 
  )
}
window.auth.CONNECT_manage_connect();

It can work for 10 minutes without a bug or crash right after a new page is opened ā€¦ Hereā€™s a screenshot of the logs if it can provide any guidance:

The token is valid for 10 min, and I want to authentify the user once, when he opens any page of the app.

The thing I struggle to understand is that the error comes after the page is loaded & the authentification is done, and is emitted by one of the functions in Dash library, which makes quite hard to debug ā€¦

Once again, thank you very much for your valuable help on this :slight_smile:

My guess is that your authentication is working, and it is stopping when your token expires.

You probably need a way to automatically refresh your token. :grin:

I thought about that for a moment too, but the ā€œfetch errorā€ can happen right after the page is loaded and the authorization is requested though ā€¦

Hmm, make sure that your fetch request to refresh the token happens before the time expiry.

So, you have 10 mins, at 9.5 is sends the new request to refresh the token.

If you are sending it simultaneously, then it makes sense for your request to fail. Also, it might be good to poke a hole through your auth to allow the generic things like component suites and others to pass through.

I will try your first suggestion, many thanks !

Do you have any idea on how to poke a hole through my auth please ? I must admit I donā€™t really see how to do it right now, any help on this is more than welcome

In dash, how are you making sure your requests are authenticated?

This is done by another file Iā€™m loading in my dash app with the argument external_scripts = [ā€œexternal_script_of_my_organizationā€] when I declare it.

I am not sure if that answers your question though but Iā€™m trying to give as much context as possible ā€¦

My guess is that youā€™ll have to add some exceptions to allow requests to get through that are going to specific endpoints.

Iā€™ve been digging a bit more into the browser logs too, seems there is an issue with one of the cookies.
Do you think that might be a possibility ?

I dont know what your requests look like, I also dont know what your endpoint is for the token. XD

But yeah, that could be the issue, however, this shouldnt be an issue with Dash.

Finally managed to ā€œsolveā€ this !
Turns out that if you are running your app with debug=True, then encountering this error will make the app freeze.
However with debug=False it keeps running as expected even when the fetch error appears ā€¦

1 Like