Learning Google Authentication in Streamlit the hard way


I have suffered 3 weeks on this Google auth in Streamlit thing, my mind is not ready to make a video about it so here's a link to tinker with the source code instead.

See below for a longer text recap!


Integrating Google OAuth2 Authentication to Streamlit has been an issue for some time, with multiple solutions being developed like streamlit-oauth, st_oauth's prototype by the Streamlit team or streamlit-google-auth.

But for the past months, I have been building myself a multipage Youtube analytics Streamlit app. To authenticate and download my Youtube statistics, I use the native google-auth-oauthlib library.

I did all of this with zero understanding of OAuth2, and you can too! Then add the "Python backend developer - added Google backend auth for frontend Streamlit" role into your resume eheh.

Prerequisites - Download a OAuth Client credential

Any Google operation is executed from a Google Cloud Platform (GCP) project. Create one from the Google Cloud Console with a Gmail account, then create a new OAuth Client credentials.

My Streamlit app will generate a Flask server on http://localhost:9000, or a FastAPI server will be running on http://localhost:8000 to catch back authorization codes from Google after signing in. You need to declare them in the `Authorised redirect URIs` part.

The credentials UI will ask you to edit the OAuth consent screen for:

  1. your email and app descriptions
  2. which permissions your app asks for from the user like profile picture, Google Calendar access or Google Drive editing, and
  3. who is going to use your app (test users, emails from a Google Workspace or a general audience, in which case you will need to submit your app to Google verification depending on the sensitivity of the permission scopes you ask for)

Then download the client_id, client_secret and JSON key to load into Streamlit.

Running locally or on-premise for a few users? Just use google-auth-oauthlib

google-auth-oauthlib has get_user_credentials, which is a high-level API for run_local_server. It basically does the whole OAuth2 flow for you locally by spawning a Flask server to catch all the flow callbacks for you, and gets you back a Credentials object which contains an ID Token. The ID Token contains the user profile information like the email, name and an URL to the profile picture, things that are easy to display in Streamlit and store in a database for future usage.

Google actually has a quickstart and a repo full of Python auth examples you can read from.

The downside is it won't easily deploy on Streamlit Cloud, as you won't have the rights to spawn a new Flask process, and the port to Flask will be blocked. You can't access Streamlit's backend Tornado server to add those OAuth redirect endpoints either...though this is being discussed in the Native Authentication issue and will absolutely be useful for other purposes like catching a callback event after a Stripe payment.

In the meantime, you can replace the auto-spawned Flask server with your own Flask or FastAPI to run the flow and get back the ID Token. And I've wanted to play with FastAPI for a while so I gave it a try.

The more robust but hardcore way: Server-side FastAPI and Cookies

Reading about secure OAuth authentication, you quickly stumble upon the topic of "Session Cookies vs JWT". This is probably a topic you will read about if you want to dig deeper into backend engineering.

What I didn't realize is I built my own Session Cookie solution where:

  1. I store the ID Token keyed by a state key in a database,
  2. send the state key to Streamlit stored as a HTTP only cookie,
  3. and request user info stored in the ID Token through FastAPI by sending the cookie info.

That way the sensitive ID/access/refresh tokens are never sent back to Streamlit, and FastAPI acts as the middleman between the frontend state cookie and the backend user info+tokens.

This relies on 3 hacks that hopefully Streamlit will solve soon:

  • There is no way to redirect a user to an URL, like the Google Authorization URL which lets you sign in with your Google account. Though there is a workaround using the infamous IFrame breaking technique.
  • No custom endpoints on Streamlit, we replace this with FastAPI running in parallel
  • Wait...we don't need hacks to access cookies since Streamlit 1.37 with st.context.cookies. FastAPI after authenticating a user can redirect to our Streamlit app with a cookie containing the state key using RedirectResponse.

This was fun to implement and try to understand how to:

  • deploy FastAPI separately to handle as a separate authentication service, and maybe add a Caddy reverse proxy in front of Streamlit and FastAPI
  • revoke a session by destroying the state key in the database and cleaning the cookie on the Streamlit frontend side
  • prevent CSRF attacks using the state key

But I'll be honest: if I take my data scientist hat who doesn't want to do CSS Flexbox nor OAuth 2 in Streamlit, I really don't want to maintain this FastAPI+Cookie to Token management.

This FastAPI+DB combo can be replaced by Auth As A Service solutions like Auth0, Supabase Auth or Firebase Authentication. They also have features like multiple platform sign-in, more security best practies and drop-in UI.

I should ask them for a sponsored video 😂

What about in a Javascript Component embedding the drop-in UI?

If you saw my recent video about giving up on my published Streamlit Components, you know I tend to recommend building a Streamlit Component to host a JS library as long as it doesn't require you to write too much additional logic.

Good news, you can embed the Google Sign-in or Firebase UI into a Streamlit Component. It uses Javascript callbacks instead of redirect URIs that require your Tornado/Flask/FastAPI, and the JS code will send the ID Token back to the Streamlit backend so you can store it in a user database before showing the info.

I haven't tested this solution enough, though there is a draft in my Github project. But when I deployed this on Streamlit Cloud, I got hit with CSRF issues. I'm guessing because the component embeds the Authentication UI in an Iframe, both the Streamlit app and the Component Iframe have different addresses and a CSRF security issue arises.

If I have time to test the Iframe intermediate solution, or if a backend security expert can answer this email, I'd be happy to share a solution ;)

That was a lot to digest

Definitely give it a go though, especially if you want to dive into projects that involve reading Google resources, like Google Docs to fine-tune a LLM on. Wouldn't that be an interesting side-project??

You deserve a nap after so much reading.

Brighten your Work with Streamlit/Web Apps and Social Media Videos

Upcoming Youtube tutorials, the latest updates and exclusive resources around Streamlit & friends, GIFs and animations. Directly in your inbox every ~2 weeks.

Read more from Brighten your Work with Streamlit/Web Apps and Social Media Videos

Ever needed to uniquely identify users for your Streamlit app through an email/Google single sign-on page? With version 1.42, Streamlit comes with Native Authentication support through the OpenID Connect protocol. I created a fake Streamlit store app with a sign up button for you to play with Streamlit native authentication through Auth0, an authentication as a service platform that makes it easier to manage multiple social logins like Google, X/Twitter or Linkedin. The app is still live on...

A week ago, I was sitting in an airport cafe, ready to wait 3 hours for a plane from Manchester back to Paris, followed by a 4 hour train trip to the South of France. Those long business trips are my usual gateway to catch up on "things I want to learn but never have time to". I would load up on hour-long entrepreneurship podcasts and rediscover books about Marketing from Seth Godin. But the morning in this airport cafe, I stumbled upon Next.js's File-based routing system. Each folder maps to...

I just heard the Solara team unveiled a new platform to deploy Dash, Solara and Streamlit apps! py.cafe, to share Python apps while on a coffee chat WASM Platforms py.cafe is a new WebAssembly powered solution to host and share Python apps through the power of Pyodide. This is a fancy way to say you can implement a Streamlit meme app in your browser, then copy the URL and send it to a friend, colleague or the CEO of your dream company. Just like this link (hopefully this long weird URL is not...