Skip to content

"Error, missing provider for callback." - phantom error log on st.logout() #13101

@ruskgo

Description

@ruskgo

Checklist

  • I have searched the existing issues for similar issues.
  • I added a very descriptive title to this issue.
  • I have provided sufficient information below to help reproduce this issue.

Summary

The error log is being generated during a logout. It does not brake the the logging out sequence, the app does not fail, but the error is logged (triggering my app alerts).
I believe this is because the check for the provider is happening after the session has been cleared and a new one created.
There maybe a specific need for the error handler to check the provider in the session, I am not sure, but I wonder if it should be checking for the provider in the secrest.toml config instead.

Here the function I am referring to: https://github.com/streamlit/streamlit/blob/develop/lib/streamlit/web/server/oauth_authlib_routes.py#L164

Reproducible Code Example

import logging
import os
import time

import streamlit as st
import yaml

from auth import Auth

# For a local run using .env file
if os.environ.get("ENV_SHORT", "LOCAL") == "LOCAL":
    from dotenv import load_dotenv
    load_dotenv()

logging.basicConfig(level='INFO')
logger = logging.getLogger('app.py')


def sso_login():
    """Re-direct the user to Microsoft SSO."""
    if st.session_state["env_short"].lower() == "uat":
        st.title('Streamlit Forms - Testing')
        st.warning("This is a **Testing** environment!  \nUse this link for live environment: https://...")
    else:
        st.title('Streamlit Forms')

    if st.button("Log in with Microsoft"):
        st.login("microsoft")
    st.stop()

def logout():
    """Clear the cookies and session state vars."""
    st.session_state.groups = []
    st.logout()
    st.info("User logged out.")

def forms_pages(groups):

    logoutPage = st.Page(
            logout,
            title="Log out",
            icon=":material/logout:",
        )
    noAccess = st.Page(
            no_access,
            title="Home",
            icon=":material/home:",
        )

    try:
        with open('forms/config.yml') as file:
            forms_config = yaml.safe_load(file)

        forms_config = forms_config['forms']

        formsPages = []

        # Check if the the user is part of the azure groups included in the Forms config.yml file.
        group_set = set(groups)
        for form, values in forms_config.items():
            groups_ids = [group['id'] for group in values['groups']]
            groups_match = group_set.intersection(set(groups_ids))

            # Create the Form page for each match between the config.yml and azure groups the user is member of.
            if len(groups_match) != 0: 
                formsPages.append(
                    st.Page(
                        f"forms/{form}.py",
                        title=values['title'],
                    )
                )
        # If there is no match, point the user to the No Access and Log Out pages to request the access
        if len(formsPages) > 0:
            pg = st.navigation(
            {"Home":formsPages} | {"Account":[logoutPage]}
            )
            pg.run()
        else:
            pg = st.navigation(
            {"Account":[noAccess, logoutPage]}
            )
            pg.run()


    except Exception as error:
        logger.error(f"Something went wrong during the creation of the Forms pages: {error}")
        


# Set up variable for session expiry in 8 hours.
current_time = int(time.time())
hours_in_session = 8 * 60 * 60


# Set up environement:
if "env_short" not in st.session_state:
    st.session_state["env_short"] = os.environ["ENV_SHORT"]

if "groups" not in st.session_state:
    st.session_state["groups"] = []


# Access checks
try:
    if "error" in st.user:
        st.error("Something went wrong, please **Try Again** or **Raise a Ticket** if the error persists.")
        logger.info(f"Something went wrong during logging attempt via SSO: {st.user.to_dict()}")
        log_again = st.button("Try Again",type="primary")
        if log_again:
            st.logout()
        raise_a_ticket = st.button("Raise a Ticket",type="secondary")
        if raise_a_ticket:
            logger.error(f"Something went wrong during logging via SSO: {st.user.to_dict()}")


    elif not st.user["is_logged_in"]:
        if st.session_state["env_short"].lower() == "uat":
            page_title = 'Streamlit Forms - Testing'
        else:
            page_title = 'Streamlit Forms'

        pg = st.navigation(
            {"Home":[
                    st.Page(
                    sso_login,
                    title=page_title,
                    icon=":material/login:"
                    )
                ]
            }
        )
        pg.run()

    # Delete `st.user` cookies if stayed logged over two days.
    elif current_time > (st.user.exp + hours_in_session):
        logger.info(f"Current time is greater than the exp date. Unicode time: {st.user.exp}. Logging out.")
        st.logout()

    # Render corresponding Forms pages if groups already stored in the state.
    elif len(st.session_state["groups"]) > 0:
        logger.info(f"Groups stored in session state. Number of groups:{len(st.session_state.groups)}")
        forms_pages(st.session_state["groups"])

    # Get list of groups. 
    elif len(st.session_state["groups"]) == 0:
        logger.info("Large group list, calling the API to get all the groups...")
         
        client_id = st.secrets["auth"]["microsoft"]["client_id"]
        client_secret = st.secrets["auth"]["microsoft"]["client_secret"]
        tenant = st.secrets["auth"]["microsoft"]["tenant"]
        auth = Auth(client_id=client_id,client_secret=client_secret,tenant=tenant)

        st.session_state["groups"] = auth.get_groups(st.user["oid"])
        logger.info(f"Fetched the large group list. API response object type: {type(st.session_state['groups'])}.")
        forms_pages(st.session_state["groups"])

except Exception as error:
    st.error(
        "Something went wrong, please **Try Again** "
        "or **Raise a Ticket** if the error persists."
    )

    log_again = st.button("Try Again",type="primary")
    if log_again:
        st.logout()
    raise_a_ticket = st.button("Raise a Ticket",type="secondary")
    if raise_a_ticket:
        logger.error(f"Something went wrong during the Access Check: {error}")

Steps To Reproduce

  1. Log in
  2. Refresh browser
  3. Logout

Expected Behavior

Since the provider is available in the secrets.toml there should not be an error log when a session is reset.

Current Behavior

Error Message:

Error, missing provider for oauth callback

Is this a regression?

  • Yes, this used to work in a previous version.

Debug info

  • Streamlit version: 1.50.0
  • Python version: 3.11
  • Operating System: debian:13
  • Browser: Edge (might be others too)

Additional Information

This issue didn't occur prior version 1.47.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature:authenticationRelated to user authenticationfeature:st.loginRelated to `st.login` commandpriority:P3Medium prioritystatus:confirmedBug has been confirmed by the Streamlit teamtype:bugSomething isn't working as expected

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions