Streamlit Next.js Component, Auth0 Authentication, Bi-Directional Messaging & Serverless APIs

We can connect w/c 5 July. If you look at the Auth0 “connection” setup docs you’ll see that configuring social logins is just a matter of clicking a checkbox in the connections management portal, and then the different options will be added to the Auth0 login box automatically.

Thank you for your initiative @asehmi. Streamlit is a great framework but is sorely lacking out of the box auth functionality for certain types of projects.

I have tried running your project locally but noticed that the sessionInfo data written in the Indexed Database pertaining to the auth popup window is not being added to the Indexed Database in the main tab (inspector shows the Indexed DB as empty and token data is never picked up in getSessionInfo). Am I missing something?

Hi @keith,

When the Auth0 login/logout completes, a redirect to index.js is performed. Here getServerSideProps() obtains the context session object and passes it to Auth.js which stores (on login) or deletes it (on logout) in local storage. index.js/Auth.js is just a mini-app which runs in a pop-up window on the same domain & port as the Streamlit component (localhost:3001). The component runs in the Streamlit page and its main responsibility is simply to monitor for local storage session object changes and notify the Streamlit client/host – see the one or two second heartbeat which looks for these changes (note, it’s allowed to do so only because it runs on the same domain + port as index.js). When the Streamlit client receives a token update, it stores it in session_state (not local storage, as I don’t think Streamlit has access to this anyway, and also it runs on a different port [I use 4010, I think], so can’t see local storage on port 3001). My published code uses the session state hack, not the latest st.session_state.

In streamlit.tsx > getSessionInfo(), you may need to de-serialize the stored session string, if and only if, its stored by Auth.js as a string.

Here’s what I have in Auth.js:

  useEffect(async () => {
        if (sessionInfo.user?.email) {
            console.log('AuthApp (set session info: user, token, expiry)')
            await STORAGE.setItem('sessionInfo', JSON.stringify(sessionInfo))
        } else {
            console.log('AuthApp (remove session info: user, token, expiry)')
            await STORAGE.removeItem('sessionInfo')
        }
  }, [sessionInfo])

Correspondingly in streamlit.tsx:

  const getSessionInfo = async () => {
    const sessionInfoStr = await STORAGE.getItem('sessionInfo')
    if (sessionInfoStr) {
      const sessionInfo = JSON.parse(String(sessionInfoStr))
      const me = sessionInfo.user
      if (me) {
        const name = me.name || `${me.given_name} ${me.family_name}` || me.nickname || me.email
        const email = me.email || me.sub
        const user = { user: me, name: name, email: email }
        const token = sessionInfo.token // {value: accessToken, value_id_token: idToken, expiry: tokenExpiresAt}
        console.log('STC getSessionInfo (user): ' + JSON.stringify(user))
        console.log('STC getSessionInfo (token): ' + JSON.stringify(token))
        return {user: user, token: token}
      }
    }
    console.log('STC getSessionInfo (NULL)')
    return {user: null, token: null}
  }

streamlit.tsx > listenForAuthChangeAndNotifyHost() has not changed.

Hope that helps.

P.S. I’m writing a short series of 2-3 articles for the Auth0 Developer Blog in which I’ll be introducing Streamlit and components, and then implementing Auth0 authentication. For this, I wrote a slightly newer version of the example, which uses st.session_state.

After further investigation I found that it works fine on Chrome but it seems that Safari does not support the usage of an Indexed Database when embedded in an iframe. (Using local storage instead does seem to work but I have yet to find a way how to access this value from within the heartbeat.)

Login/Logout text is now being updated as expected in the UI but am now getting a AttributeError("‘str’ object has no attribute ‘get’") from component_event_producer() or more specifically component_host(key=key, **props) or {}. Any pointers as to where I might look to fix this? Below is the last debug message that was logged.

streamlit.script_runner.RerunException: RerunData(query_string='', widget_states=widgets {
  id: "$$GENERATED_WIDGET_KEY-4884191662795434989"
  bool_value: false
}
widgets {
  id: "login"
  json_value: "{\"name\":\"onStatusUpdate\",\"data\":{\"hostname\":\"None\",\"message\":\"Logged in\",\"isError\":false,\"error\":null,\"sessioninfo\":{\"user\":{\"user\":{\"nickname\":\"username\",\"name\":\"user@hotmail.com\",\"picture\":\"https://s.gravatar.com/avatar/some_pic.png\",\"updated_at\":\"2021-07-19T22:03:29.094Z\",\"email\":\"user@hotmail.com\",\"email_verified\":true,\"sub\":\"auth0|60e45bf6011d8d006a059cca\"},\"name\":\"user@hotmail.com\",\"email\":\"user@hotmail.com\"},\"token\":{\"value\":\"TOKEN_HERE\",\"value_id_token\":\"TOKENHERE\",\"expiry\":1626818609}}}}"
}
)

(On a sidenote I had trouble finding a working ptvsd package for python 3.7 and settled for ptvsd==5.0.0a12. I also needed to perform some exception handling on ptvsd.enable_attach(address=('localhost', 6790)). This was failing when attempting to enable_attach() more than once for the same process).

Looking forward to your Auth0 articles.

Glad you’re making progress. I hadn’t tried it on Safari, so good catch! Presumably you adjusted the driver used by localforage in storage.js.

I think the code that comes after component_host(key=key, **props) or {} breaks if the returned event isn’t always a dict. I had the same issue so implemented some belt and braces. Please, try these lines instead of that single line:

    event_ = component_host(key=key, **props)

    event = {}
    if isinstance(event_, str):
        event = json.loads(event_)
    elif isinstance(event_, dict):
        event = event_

Below updated by author

Regarding the issue with ptvsd, I found that debugpy has replaced it.

Essentially, follow these steps:

  1. pip install debugpy
  2. Add the following snippet in your <your-app_name>.py file.
try:
    import debugpy
    debugpy.listen(("localhost", 5678))
    # debugpy.wait_for_client() # Only include this line if you always want to manually attach the debugger
except:
    # Ignore... for Heroku deployments!
    pass
  1. Then start your Streamlit app

streamlit run <your-app_name>.py

  1. From the VS Code Debug sidebar menu configure Python: Remote Attach debug server and update your launch.json file with the details below.
{
    "name": "Python: Remote Attach",
    "type": "python",
    "request": "attach",
    "port": 5678,
    "host": "localhost",
    "justMyCode": true,
    "redirectOutput": true,
    "pathMappings": [
        {
            "localRoot": "${workspaceFolder}",
            "remoteRoot": "."
        }
    ]
}
  1. Make sure you manually insert the redirectOutput setting.
  2. By default you will be debugging your own code only. If you want to debug into streamlit code, then change justMyCode setting from true to false.
  3. Finally, attach the debugger by clicking the debugger play button.

Don’t know where you’re based (I’m in UK), but would be great to have a web conf call to see how you’re using this?

Cheers,
Arvindra

1 Like

This is very nice, I will definitely start using this in my next app, as good quality and easy to use authentication libs are rare, thank you for this nice work.

1 Like

That seems to have fixed it, thanks.

I’m based in Europe, just reached out via LinkedIn.

1 Like

I’ve edited the solution to include info on replacing ptvsd.

1 Like