How I Added Google Auth To a Streamlit App (It Didn't Go Well)

Поделиться
HTML-код
  • Опубликовано: 16 ноя 2024

Комментарии • 50

  • @brianmorin5547
    @brianmorin5547 2 месяца назад

    I did a "tour of duty" on this recently. I got Google OAuth working with a similar approach you mentioned at the end by doing the flow directly between streamlit and Google OAuth. After fetching email via the authorization URL that comes back, I make the call to my Flask backend with that email to get a JWT token to act as refresh token ... where I ATTEMPT to store cookie or localStorage
    All attempts to do the latter work fine when testing locally but ALL fail in cloud deployments. I have tried a few different cloud platforms and a few different streamlit components as well as creating my own. I'm at my wits end. I can't bare for people to use my app since they have to constantly log in creating a terrible experience.
    Did you stumble on a solution for storing a token in a cookie or localStorage that persists in cloud deployments? Will an upgrade to 1.37.0 with HTTP Only and Fast API solve this?? I'm 1.35.0

    • @andfanilo
      @andfanilo  2 месяца назад

      Hey there! So I tried on my side too, and you're right the cookies always got deleted in the redirection from fastapi to streamlit, and I spent 4h on this argh thing and...apparently because of stackoverflow.com/questions/73547776/how-to-redirect-from-one-domain-to-another-and-set-cookies-or-headers-for-the-ot I learned you can only set cookies from `abc.example.test` to another `xyz.example.test` if they both have the same parent domain `example.test`
      So...I created a GCP Compute Engine, I installed Streamlit and FastAPI in the VM, and also installed a Caddy reverse proxy that will run in front of both Python apps to add HTTPS automatically
      I bought a domain name (let's call it example.test) and added Cloudflare DNS records for both streamlit.example.test and fastapi.example.test to point to the static IP of the VM.
      I then have Caddy in the VM redirect streamlit.example.test to the Streamlit app and fastapi.example.test redirect to the FastAPI app. This is only to simulate 2 URLs pointing to 2 different backends.
      Then in FastAPI in the redirect after the auth, I get the token, and set the cookie on the redirect with the parent domain:
      ```
      response = RedirectResponse("streamlit.example.test")
      response.set_cookie(
      "__streamlit_access_token",
      access_token,
      domain="example.test",
      expires=datetime.now(timezone.utc) + timedelta(minutes=15),
      secure=True,
      httponly=True,
      samesite="none", # don't know if I should edit this one to "lax" btw
      )
      ```
      It should transit securely thanks to secure=True + Caddy's automatic HTTPS
      Then with Streamlit 1.37, st.context.cookies gets the key __streamlit_access_token with the access token inside
      ```
      credentials = Credentials(st.context.cookies["__streamlit_access_token"])
      ```
      Then you can send the refresh token with a longer expire date and send it through Credentials to refresh the token, though I didn't test it.
      ---
      this is the only thing that worked out for me, and I don't have to login again o.O this is amazing!
      I think you're fine with Flask, either make_response redirecting to Streamlit with a cookie after the auth, or Streamlit making a request to Flask for a given state to fetch back cookies. I wouldn't try to set cookie from a Streamlit component or streamlit_js, seems like too much trouble, better use the native cookie setting from Flask response.
      Then upgrae Streamlit to 1.37 and use st.context.cookies
      Hope it helps, good luck! (I'll try cleaning this up to post on Github and to my email list)

    • @andfanilo
      @andfanilo  2 месяца назад +1

      Link to project: github.com/andfanilo/streamlit-fastapi-auth-gcp

    • @brianmorin5547
      @brianmorin5547 2 месяца назад

      ​ @andfanilo Is there a support group for streamlit Auth survivors? I thought I was losing my mind on how deeply I was going as someone who is NOT an auth expert and thought I would never have to be. 2-weeks into this battle, I can write a book.
      I tried a nearly identical approach setting up a keycloak server on digital ocean. And after losing a day to the complexities of running it securely behind Nginx reverse proxy with terrible documentation, I got it to work only to fail one inch from the goal line where I could not get the query parameters to populate in streamlit. A known bug with st. DOA
      Yesterday I finally figured out how to get a token to persist by accessing the browser localStorage route using streamlit_js with some help from a community member and a pot of coffee. It works on any cloud and browser I tested.
      ...and only now I'm understanding 1.37 supports http-only which I had tried and failed at the beginning of my auth journey since I'm on 1.35... Is 10am too early for shots? I'm interested to know if the cookie actually persists the sesson in cloud deployments across browsers. If it does, I'll follow suit
      LAST! I need to take a closer look to understand how you got your Google Auth working on streamlit cloud. Mine works on any cloud deployment except streamlit's. When I click the OAuth button, I immedately lands on a 403 page instead of a redirect where I can grab the JS origin like you did

    • @brianmorin5547
      @brianmorin5547 2 месяца назад

      @@andfanilo Star

    • @andfanilo
      @andfanilo  2 месяца назад

      > Is there a support group for streamlit Auth survivors?
      I feel you :) well, auth is a pain, and not only in the Streamlit/Python community. I'm also reading difficult auth implementations in the React/Svelte community. I find most auth docs are hard to read when it's your first time doing it. So I guess everyone suffers on auth...
      > Yesterday I finally figured out how to get a token to persist by accessing the browser localStorage route using streamlit_js
      Congrats! Though I don't spend as much time on the forum, I did read on the full thread and there were a lot of cool insights! I think this solution will work in the long run
      > I'm interested to know if the cookie actually persists the sesson in cloud deployments across browsers. If it does, I'll follow suit
      Won't have time to test in the coming weeks *holidays cough*, but as long as the (top-level) domain URL for your deployments doesn't change, and if domain is properly configured when you set the cookie, the same cookie should be sent from the browser to the Tornado server. So I don't see why st.context.cookies wouldn't.
      If you do test it out, I'm interested to hear if my hypothesis is true :)
      > closer look to understand how you got your Google Auth working on streamlit cloud (with SignIn Button)
      I was excepting the Google Sign in Streamlit component to NOT work on Streamlit Cloud, because it's embedded in an and anything related to auth in an embedded just becomes a hot mess.
      In the end it worked. But I'm afraid any change to Google's sign-in security standards could render the component useless, so I'm thinking of sticking to native cookies or streamlit_js+localStorage.
      My next step, though probably in the coming months, is to integrate Stripe subscription linked to an email in Streamit, that would be fun.
      ---
      Good luck! Looking forward to the end of your stories!

  • @saminyead1233
    @saminyead1233 3 месяца назад +3

    Oh man! Only God knows how much time and effort I had to put into integrating Google OAuth into a Streamlit app. I really wish I had this video back then! Thank you so much for covering this!

    • @andfanilo
      @andfanilo  3 месяца назад +1

      I feel you, it was hard to research this video, there's a lot of documentation that just goes everywhere 😅 and LLMs kept giving me different directions...
      I never want to do auth again ahah

  • @mahmoudmagdy5572
    @mahmoudmagdy5572 3 месяца назад

    Brother , i spent 2 weeks on this google oauth , this video is a lifesaver for me , Thank you.

    • @andfanilo
      @andfanilo  3 месяца назад +1

      Good luck! Authentication is a pain 😅 but I feel you're ready to take on any challenge after you succesfully implement one, so keep pushing!

    • @mahmoudmagdy5572
      @mahmoudmagdy5572 3 месяца назад

      @@andfanilo it was a multi step process, auth then access drive then read and write from drive and keep the app flow , so gauth was the pretty much the first step , so that really solved almost 80% of my problems , appreciate ur efforts sir.

    • @АлександрЗаплатников-о6ф
      @АлександрЗаплатников-о6ф 3 месяца назад

      Also spent 2 weeks to find the way of integrating yandx authorization

  • @wreck-it-rouse
    @wreck-it-rouse 14 дней назад

    For my app, I needed to get access to the user's Google Analytics properties, so I don't think the JavaScript button is going to work for me, though I admit it's a neat way around. Instead, I found that using a Service Account was a viable solution, not least because it gives my app permanent access and keeps permissions management within Google Analytics. It requires the user to set up a Project within the Google Cloud Console and create a Service Account, and then provide a copy of the JSON file to my app to store for them and use. They can then give access to certain properties to the service account. This all works for us because it means no middleware required, and it means the senior people in the large organisation I am building this app for don't have to keep re-authenticating the connection.

    • @andfanilo
      @andfanilo  13 дней назад

      @@wreck-it-rouse yup! It works too 🙂 as long as you take all the necessary security measures so they are not easily compromised (there’s a very long best practices page about it that I read multiple times when I was uploading service account keys to Looker so it connected to external bigquery projects. cloud.google.com/iam/docs/best-practices-service-accounts )

  • @aaronsteers
    @aaronsteers 2 месяца назад

    Great video as always! Thank you!!

    • @andfanilo
      @andfanilo  2 месяца назад

      @@aaronsteers thanks for the support😁 I’m never doing auth again ahah!

  • @PrarthitShah-r6c
    @PrarthitShah-r6c 2 месяца назад

    Great Work Fanilo
    You are Awsome Brother

    • @andfanilo
      @andfanilo  2 месяца назад

      @@PrarthitShah-r6c thanks for the support 🙂 see you on the next one !

  • @ander-sonmorillo6360
    @ander-sonmorillo6360 2 месяца назад

    it is really useful to know diferents ways to authenticated.
    I would love to see the authentication method using third parties plantforms lile auth0, firebase etc

    • @andfanilo
      @andfanilo  2 месяца назад

      @@ander-sonmorillo6360 thanks for the support!
      I would love to try it too, I should ask them to sponsor a video so it gives me more motivation 😆 right now I’m burnt out from authentication problems (*´ー`*)

  • @VinothM-e4m
    @VinothM-e4m Месяц назад

    i got an error Access blocked: This app’s request is invalid......?

  • @apaul31
    @apaul31 Месяц назад

    Thanks for the video, is there an example of integrating of Auth0 with streamlit that you can share?

    • @andfanilo
      @andfanilo  Месяц назад

      github.com/kajarenc/stauthlib I was shown this beta demo with Auth0, I know it works though I haven't tested it myself
      If it's similar to Google, you can try it locally by completing the auth0 section of .streamlit/secrets.toml.example with the auth0 secrets, renaming it to secrets.toml, using localhost:8501/oauth2callback as a redirect URI and finally calling st.experimental_user.login(provider="auth0")

  • @mohammed333suliman
    @mohammed333suliman 2 месяца назад

    Great thank you

  • @Simar2222
    @Simar2222 3 месяца назад

    Hi Fanilo, It might be a noob question, but can't we use Firebase for google Authentication? What would be the pitfalls of that? Also for the authentication to work after hosting the app online

    • @yixian0716
      @yixian0716 3 месяца назад

      I am thinking about this too

    • @andfanilo
      @andfanilo  3 месяца назад +2

      Hey, great remark! Yes, we could Firebase Auth that :) I mention it at the end
      Didn't include a tutorial in the video because it was already long and there's many details for non-backend ppl...
      but I actually tried both Firebase admin server side Session Cookie from ID Token (still implies a Google authentication to fetch the token) and Firebase UI in component (you can find it in my repo)
      Things I liked: easier user & session management with multiple identity providers, easy to mix email and Google auth, integrates well with Firebase Cloud Functions to react to user creation or payments linked to account. Free tier is awesome.
      Things I disliked: it's very geared towards frontend devs, so there's a lot of tips for login from the JS side but not a lot if you want to do auth from the Python backend side. IMO you need to build a Firebase Streamlit component to get the full Firebase auth experience, and I didn't want to maintain one (I have a video on dropping my frontend drawable canvas...).
      Couldn't do Google auth with only Python Firebase, I had to authenticate in Google first then pass the ID Token to Firebase Admin SDK to get a session/cookie.
      If you are courageous, build that tailor-made Firebase UI Streamlit component, you will also get configuration to persist Auth State locally or by session through cookies ( firebase.google.com/docs/auth/web/auth-state-persistence ), they should be preserved by the browser and user should stay authenticated. Mch easier than managing it yourself like I did in the hardcore part. But I didn't try as far so this is speculation.
      I hope it makes sense! Damn I really write way too long responses I need to be more concise!

    • @Simar2222
      @Simar2222 3 месяца назад

      @@andfanilo Thanks a lot for your detailed response, I'm curious to try it out as I'm sort of doing a project that requires auth in streamlit for my masters thesis. Much appreciated 👏

    • @eurojourney
      @eurojourney 2 месяца назад

      @@andfanilo, long time fan of your channel and thank you for so many great tutorials. I am not an expert at all on this area, but I got Firebase to work for me, at least that's what I think I did. I am using GCP to host my app using Cloud Run for deployment and I am handling authentication via Firebase. So far everything seems to work but watching this video is worrying me that maybe something is not quite right... maybe? My app at this point is a demo app that connects to different GCP services (GCS, Bigquery, Secret Manager, Cloud Run, etc.) and is simple, it just shows on screen for testing purposes some data I stored in GCS and Bigquery. I am using it as sort of a template or test bench for other actual more complex data analytics apps I am building. I used Firebase as mentioned and I can login, sign up and change passwords without any issues, at least as far as I can tell, everything works well. Also, I was able to customize the sign in/sign up/change password screen to fit the look and feel of the rest of the app. Is there something I should be particularly wary about? I'll post below the function I use to login a user assuming the user already created an account and after initializing the firebase app (if you have a moment, let me know your thoughts):
      def sign_in_user(email, password, firebase_api_key):
      try:
      api_key = firebase_api_key # Replace with your Firebase project's Web API Key
      signin_url = f"identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={api_key}"
      payload = json.dumps(
      {"email": email, "password": password, "returnSecureToken": True}
      )
      response = requests.post(
      signin_url, headers={"Content-Type": "application/json"}, data=payload
      )
      response.raise_for_status()
      id_token = response.json()["idToken"]
      # Get user info using ID token
      user_info_url = (
      f"identitytoolkit.googleapis.com/v1/accounts:lookup?key={api_key}"
      )
      payload = json.dumps({"idToken": id_token})
      response = requests.post(
      user_info_url, headers={"Content-Type": "application/json"}, data=payload
      )
      response.raise_for_status()
      user_info = response.json()["users"][0]
      # Check if email is verified
      if user_info["emailVerified"]:
      st.success(f"User {email} signed in successfully")
      return user_info
      else:
      st.error(
      "Email not verified. Please check your email to verify your account."
      )
      return None
      except requests.exceptions.HTTPError as e:
      st.error(
      "Invalid email or password. Please check for errors and try again." # {e.response.json().get('error').get('message')}"
      )
      return None
      except Exception as e:
      st.error(f"An error occurred: {e}")
      return None

    • @andfanilo
      @andfanilo  2 месяца назад

      Hey, thanks for the support :)
      I have yet to try Google Identity Platform -which yeah, Firebase Auth is based on-. Thanks for providing a code snippet for the REST endpoint.
      Not a security expert either, but as long as everything is under HTTPS, which I think it is since most GCP services you are using expose HTTPS right...you should be fine passing credentials in payload. That's the point of the REST endpoint
      But I'm always wary of directly manipulating user credentials, since I could inadvertently print user and password in logs..hence the goal of this video to manipulate tokens and delegate passords to the Identity Provider.
      If you prefer manipulating passwords yourself with the endpoints, maybe you need to hash+salt passwords before sending, comparing and storing passwords. The usual password management best practices.
      I seem to remember Firebase encrypt passwords in its database, but I don't remember if you need to compare hashed values or raw password values.
      TLDR; I don't think there are any issues, but since you're taking over the credentials manipulation, just make sure you follow the security best practices on password manipulation

  • @abhishekpatta3949
    @abhishekpatta3949 2 месяца назад

    The video was very helpful but after clicking on the login button I am getting the error like Access blocked: This app’s request is invalid could you help with this problem

    • @andfanilo
      @andfanilo  2 месяца назад

      I find those errors the most difficult to debug 😫 if this is displayed on the Google signin dialog window, there should be an "error details" below the access blocked, and it generally gives you a more specific error like "misconfigured URI redirect" for example...

    •  2 месяца назад

      @@andfanilo You must have the email account you are testing with registered as a test account. At the beginning of the video you will see the process, check it again.

  • @yugandharippili1566
    @yugandharippili1566 3 месяца назад +1

    Can we do with Microsoft auth ?

    • @andfanilo
      @andfanilo  3 месяца назад

      Hey! Didn't try nor dive into MS so it's all only quick Googling...but the OAuth flow in Flask/FastAPI with MSAL learn.microsoft.com/en-us/entra/identity-platform/quickstart-web-app-python-flask?tabs=windows may be similar to the hardcore part of my video. Or Authlib ( docs.authlib.org/en/latest/client/oauth2.html#oauth2session-for-authorization-code ) if you can find the MS OAuth Authorization endpoint.
      Have Streamlit ping Flask/FastAPI for the user info and let the API manage the auth URL, tokens and redirect flow.
      Maybe if you browse through the library for "authorization code flow" there is a higher-level method that pops the Flask server for you like what happens at the start of my Google tutorial. In that case you could put everything in Streamlit like the first part of my video. learn.microsoft.com/en-us/entra/msal/python/getting-started/acquiring-tokens#acquire-token-by-authorization-code-flow seems a good start.
      But damn that looks like yet another deep dive to figure out...

  • @arnabneogi4237
    @arnabneogi4237 Месяц назад

    can you please create a demo on Azure AD, too please?

    • @andfanilo
      @andfanilo  Месяц назад

      I'll see if I can find some resources. Microsoft Azure is in my to try list but I've yet to dig into it

  • @cavitcaglartosun3246
    @cavitcaglartosun3246 3 месяца назад

    How can we achieve the same using AWS?

    • @andfanilo
      @andfanilo  3 месяца назад

      Hello! I've never tried AWS so I can't tell you the exact steps but I do hope there's a way 🤔
      Couldn't google a dedicated Python OAuth2 dedicated Cognito function like GCP has, so you may have to do it manually with Authlib/requests-oauthlib configured with the correct authorization endpoints docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html .
      Or you'll have to search for AWS oauth2 python (and maybe authorization code grant or web app flow, though desktop flow may also get you somewhere).
      Sorry I don't have many more leads since I really don't know AWS, if I find moire guides I'll push them here or in my email list. Have a nice day!

  • @NgynAn-dg3kp
    @NgynAn-dg3kp 2 месяца назад

    6:40 Le Sserafim meme :))

    • @andfanilo
      @andfanilo  2 месяца назад +1

      @@NgynAn-dg3kp 😏 eheh it’s fun to put them in videos and see who notices!

  • @sledziu32
    @sledziu32 2 месяца назад

    try integration with MS Office account. and try not to cry

    • @andfanilo
      @andfanilo  2 месяца назад

      Uuurgh good luck 😖 Streamlit to read sharepoint file metadata would be nice … what are you doing it for?

    • @sledziu32
      @sledziu32 2 месяца назад

      @@andfanilo more likely a form to submit photos, but in contrary to ms forms it should store them in ShP (not personal OneDrive) and not in folder related to question but to issued location