Google Authentication in a streamlit app

I am facing a similar issue. I am using the YouTube Data API to gather channel and video data. I have my app deployed to Streamlit but any request that requires oauth instead of an api key gives me the error below. I can run both types of requests all day in a local Streamlit server. The function to authenticate that works locally is below too.

def authenticate_youtube_api(api_version="v3"):
    SCOPES = ["https://www.googleapis.com/auth/youtube.force-ssl"]

    flow = InstalledAppFlow.from_client_secrets_file(
        "client_secrets.json", SCOPES
    )
    credentials = flow.run_local_server(port=9000, prompt='consent',
                                                authorization_prompt_message='')

    return build("youtube", api_version, credentials=credentials)
Traceback (most recent call last):

  File "/home/adminuser/venv/lib/python3.9/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 535, in _run_script

    exec(code, module.__dict__)

  File "/mount/src/concord/app_scripts/app.py", line 67, in <module>

    create_playlists_from_data()

  File "/mount/src/concord/app_scripts/app_create_playlist_add_videos_from_existing_playlist_data.py", line 161, in create_playlists_from_data

    youtube = authenticate_youtube_api()

  File "/mount/src/concord/app_scripts/app_create_playlist_add_videos_from_existing_playlist_data.py", line 43, in authenticate_youtube_api

    credentials = flow.run_local_server(port=9000)

  File "/home/adminuser/venv/lib/python3.9/site-packages/google_auth_oauthlib/flow.py", line 444, in run_local_server

    webbrowser.get(browser).open(auth_url, new=1, autoraise=True)

  File "/usr/local/lib/python3.9/webbrowser.py", line 65, in get

    raise Error("could not locate runnable browser")

webbrowser.Error: could not locate runnable browser
1 Like

Worked here!!

def auth_flow():
    auth_code = st.query_params.get("code")
    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        "./security/client_secret.json", # replace with you json credentials from your google auth app
        scopes=SCOPES,
        redirect_uri=redirect_uri,
    )
    if auth_code:
        flow.fetch_token(code=auth_code)
        credentials = flow.credentials
        st.session_state["google_auth_code"] = auth_code
        st.session_state['creds'] = credentials
        return st.session_state['creds']

I used this credential in build to connect with google sheets and Google drive too. Thanks Bro!! :grin:

1 Like

While this does work locally, it fails when posted to the Streamlit Cloud. The webbrowser does not exist - on purpose according to Streamlit Cloud people - and thus it will never open a new tab. I have now tried this approach, flow-run_local_server, and even a URL link. Nothing works when hosted.

Has anybody posted their project to the Streamlit Cloud with OAuth and had it work?

Hi @Ken_Tola, although I haven’t tested on Streamli Cloud yet I managed to get it working without webbrowser. Saw the limitation of webbrowser here. My work around was creating a link button. See my full code below for your reference.

from streamlit_js import st_js, st_js_blocking

def ls_get(k, key=None):
    return st_js_blocking(f"return JSON.parse(localStorage.getItem('{k}'));", key)


def ls_set(k, v, key=None):
    jdata = json.dumps(v, ensure_ascii=False)
    st_js_blocking(f"localStorage.setItem('{k}', JSON.stringify({jdata}));", key)

def init_session():
    user_info = ls_get("user_info")
    if user_info:
        st.session_state["user_info"] = user_info

def auth_flow():
    st.write("Welcome to My App!")
    auth_code = st.query_params.get("code")
    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        client_secret_json_path, # replace with you json credentials from your google auth app
        scopes=["https://www.googleapis.com/auth/userinfo.email", "openid"],
        redirect_uri=redirect_uri,
    )
    if auth_code:
        flow.fetch_token(code=auth_code)
        credentials = flow.credentials
        st.write("Login Done")
        user_info_service = build(
            serviceName="oauth2",
            version="v2",
            credentials=credentials,
        )
        user_info = user_info_service.userinfo().get().execute()
        assert user_info.get("email"), "Email not found in infos"
        st.session_state["google_auth_code"] = auth_code
        st.session_state["user_info"] = user_info
        ls_set("user_info", user_info)
        # TODO fix calling consecutive ls_set is not working 
        # ls_set("google_auth_code", auth_code)
    else:
        authorization_url, state = flow.authorization_url(
            access_type="offline",
            include_granted_scopes="true",
        )
        st.link_button("Sign in with Google", authorization_url)

def main():
    init_session()
    if "user_info" not in st.session_state:
        auth_flow()

    if "user_info" in st.session_state:
        main_flow()

if __name__ == "__main__":
    main()
2 Likes

Tariro –

Thank you so much for your code – I really appreciate it!

When I run your code, I get back every scope that I have ever accessed in Google with my ID – including many that I have long since disabled. This change in scope crashes the site, do you have any suggestions for how to prevent this from occurring?

The scopes I pass in are:

scopes = [“openid”, “https://www.googleapis.com/auth/userinfo.profile”,
https://www.googleapis.com/auth/userinfo.email”,
https://www.googleapis.com/auth/gmail.readonly”]

Here is the error:

Warning: Scope has changed from https://www.googleapis.com/auth/userinfo.profile openid https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/userinfo.email to “https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/blogger https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/userinfo.profile openid”.

Thank you!

Ken

Hi @Ken_Tola, I am not familiar with the error. Depending on the stage of development you are at, you can create a new OAuth 2.0 Client with the scope that you want. Sorry, can’t be of much help on this.

Worked for me!
For anyone who’s interested to make the button look official, here is the link to customize a streamlit button, and the css can be found here

Can i ask you, how do you insert js script into streamlit file?

This one works