gradio
gradio copied to clipboard
Google Authentication
In some apps, we want to filter harmful users or bots. I think that having a component that enables google authentication (rather than a username, passowrd authentication) in gradio can be very helpful.
Describe the solution you'd like
I'd like to have an authentication component that receives the details of the user so it can decide if the user may access the app or not.
This code is partial (and unfortunately non-elegant) solution. It:
- creates a new app that mounts the gradio app
- the new app is in charge of the google authentication.
A key aspect that is missed here, is that in the case that the user goes straight ahead to the gradio endpoint, there is no redirection to login and the user is stuck there. Is there a way by any chance to redirect from within the gradio demo back to \login
?
import json
from authlib.integrations.base_client import OAuthError
from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import HTMLResponse, RedirectResponse
from starlette.requests import Request
import gradio as gr
from authlib.integrations.starlette_client import OAuth
from starlette.config import Config
# CODE FOR NEW APP
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="!secret")
config = Config('.env')
oauth = OAuth(config)
CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration'
oauth.register(
name='google',
server_metadata_url=CONF_URL,
client_kwargs={
'scope': 'openid email profile'
}
)
@app.get('/')
async def homepage(request: Request):
user = request.session.get('user')
if user:
data = json.dumps(user)
html = (
f'<pre>{data}</pre>'
'<a href="/logout">logout</a>'
'<br>'
'<a href="/gradio">demo</a>'
)
return HTMLResponse(html)
return HTMLResponse('<a href="/login">login</a>')
@app.get('/login')
async def login(request: Request):
redirect_uri = request.url_for('auth')
return await oauth.google.authorize_redirect(request, redirect_uri)
@app.get('/auth')
async def auth(request: Request):
print(f"before request user {request.session.get('user')}")
try:
token = await oauth.google.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f'<h1>{error.error}</h1>')
user = token.get('userinfo')
if user:
request.session['user'] = dict(user)
print(f"after request user {request.session.get('user')}")
return RedirectResponse(url='/')
@app.get('/logout')
async def logout(request: Request):
request.session.pop('user', None)
return RedirectResponse(url='/')
# CODE FOR MOUNTED GRADIO APP
def update(name, request: gr.Request):
return f"Welcome to Gradio, {name}!\n{request.request.session.get('user')}"
def make_demo_visible(request: gr.Request):
if request.request.session.get('user'):
return gr.update(visible=True), gr.update(visible=True), gr.update(visible=True), gr.update(visible=False)
return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="Looks like you are not logged in. Please login at the main app.")
with gr.Blocks() as demo:
start_btn = gr.Button("Press Here to initialize the demo!")
with gr.Row():
inp = gr.Textbox(placeholder="What is your name?", visible=False)
out = gr.Textbox(visible=False)
btn = gr.Button("Run", visible=False)
start_btn.click(make_demo_visible, outputs=[inp, out, btn, start_btn])
btn.click(fn=update, inputs=inp, outputs=out)
gradio_app = gr.mount_gradio_app(app, demo, "/gradio")
Google Firebase offers an excellent solution to this use case. I have a useful API on the backend server data processing that handles inbound authorization requests from a Google Firebase front-end. (I would be happy to offer my code to anyone who asks.) Google Firebase front-end support is excellent. I have a React UI from previous repositories and directing users to that landing page; require them to log in with the Firebase token or via Google Login, then redirects the user to the primary front-end of the app (the Gradio UI in this case). I have not installed that into my current Gradio UI only because I am only now developing the app via Gradio, though the software has served me excellently for several years now. It would even be relatively easy to embed an email
and UUID
into an existing Gradio demo which communicates first with the backend for Google Firebase; which then, upon returning confirmation of the email-UUID match makes numerous, otherwise invisible and inactive Gradio UI blocks appear. I think that might even be easier in a first pass to do this because there is no need to mount a second service for the front-end. This solution is similar to @yuvalkirstain with Google Firebase handling the auth.
Hi @davidbernat, inroder to request permissions from google oauth. my app needs to redirect to a url provided by google api. The problem is that gradio doesn't offer a redirect feature.
Hey @yuvalkirstain , Can you give me a way to redirect the user to a given url, I'll be really happy if you can help me.
Google Firebase offers an excellent solution to this use case. I have a useful API on the backend server data processing that handles inbound authorization requests from a Google Firebase front-end. (I would be happy to offer my code to anyone who asks.) Google Firebase front-end support is excellent. I have a React UI from previous repositories and directing users to that landing page; require them to log in with the Firebase token or via Google Login, then redirects the user to the primary front-end of the app (the Gradio UI in this case). I have not installed that into my current Gradio UI only because I am only now developing the app via Gradio, though the software has served me excellently for several years now. It would even be relatively easy to embed an
UUID
into an existing Gradio demo which communicates first with the backend for Google Firebase; which then, upon returning confirmation of the email-UUID match makes numerous, otherwise invisible and inactive Gradio UI blocks appear. I think that might even be easier in a first pass to do this because there is no need to mount a second service for the front-end. This solution is similar to @yuvalkirstain with Google Firebase handling the auth.
Hey @davidbernat , I'm trying to implement exactly this, add authentication to a gradio app, pass in a UUID and make blocks visible after comparing the UUID with what's in a database. If you'd be willing to share any code or give any advice, that would be really helpful!
Has anyone made any progress on this? I would like to also have firebase authenticate users and redirect to my app once authenticated
@jerpint I found that gradio is built on top of fastapi, so for me I mounted gradio app on top of a fastapi app that handle authentication.
@zinoubm i was thinking of doing something similar, do you have a working example?
@jerpint Sorry about that, It was for a client so I can't share it. But I believe the docs have some useful resources.
@jerpint , You may find this helpful https://gradio.app/sharing-your-app/#mounting-within-another-fastapi-app
I solved this problem. Please mark this issue as complete. Starlight LLC. Thanks.
Solved? Where can I see this enhancement @davidbernat?
I just came across a case where I will need to integrate Google login to a Gradio app and stumbled upon this discussion just now.
Unfortunately until Google and Apple shift toward more open models of research and data in their AI division we have decided to close our doors to each company here at Starlight LLC and Starlight.AI LLC. You may feel free to reach out to me directly, though I wish to not discuss this project at this time as Google has shifted its backend priorities anyway, and legal action is already in discussion.
So there's no other way than to wrap it in a FastAPI app and then implement auth for that FastAPI app?
@davidbernat you've not solved anything as far as I can see. Please keep this issue open until the functionality is implemented in Gradio, or there is a simple process we can follow to make it work well.
@yuvalkirstain Have you hosted this somewhere so it'll be helpful for us to take a look?
@zinoubm I know you can't share your code, but could you explain how you access the gr.Request to get the user information? I tried the above solution by @yuvalkirstain but it only works when the queue is disabled. Enabling the queue makes request.request be None and the solution no longer works.
Any final update on this thread?
How to enable Google SSO for Gradio Apps, I tried the mount_gradio_app
function based on @yuvalkirstain suggestion, but still when I directly go to the pathway, it overrides the login and directly make it accessible.
Someone provide alternative approach for Google SSO
What if we use some Middleware from FastAPI?
The issue is not the API. All that is required is an HTML element that can create API calls via JS or, better yet, a hook into a Python function. Unfortunately, my understanding is that Gradio is not providing those yet, and I created my own.
@yuvalkirstain @jerpint @kambleakash0 @dhruv-anand-aintech I think I found a solution to enforce authentication on gradio app, using a custom middleware helps with this, here's the code of @yuvalkirstain with the middleware:
import json
from authlib.integrations.base_client import OAuthError
from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import HTMLResponse, RedirectResponse
from starlette.requests import Request
import gradio as gr
from authlib.integrations.starlette_client import OAuth
from starlette.config import Config
# CODE FOR NEW APP
app = FastAPI()
config = Config('.env')
oauth = OAuth(config)
CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration'
oauth.register(
name='google',
server_metadata_url=CONF_URL,
client_kwargs={
'scope': 'openid email profile'
}
)
# The Middleware that enforces authentication on /gradio app
@app.middleware("http")
async def check_authentication(request: Request, call_next):
if request.url.path.startswith('/login') or request.url.path.startswith('/auth'):
# Skip authentication check for login and authentication routes
return await call_next(request)
if request.url.path=='/gradio/api/predict' or request.url.path=='/gradio/reset':
return await call_next(request)
user = request.session.get("user")
if not user:
# User is not logged in, redirect to login page
return RedirectResponse(url="/login")
return await call_next(request)
@app.get('/')
async def homepage(request: Request):
user = request.session.get('user')
if user:
data = json.dumps(user)
html = (
f'<pre>{data}</pre>'
'<a href="/logout">logout</a>'
'<br>'
'<a href="/gradio">demo</a>'
)
return HTMLResponse(html)
return HTMLResponse('<a href="/login">login</a>')
@app.get('/login')
async def login(request: Request):
redirect_uri = request.url_for('auth')
return await oauth.google.authorize_redirect(request, redirect_uri)
@app.get('/auth')
async def auth(request: Request):
print(f"before request user {request.session.get('user')}")
try:
token = await oauth.google.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f'<h1>{error.error}</h1>')
user = token.get('userinfo')
if user:
request.session['user'] = dict(user)
print(f"after request user {request.session.get('user')}")
return RedirectResponse(url='/')
@app.get('/logout')
async def logout(request: Request):
request.session.pop('user', None)
return RedirectResponse(url='/')
# CODE FOR MOUNTED GRADIO APP
def update(name, request: gr.Request):
return f"Welcome to Gradio, {name}!\n{request.request.session.get('user')}"
def make_demo_visible(request: gr.Request):
if request.request.session.get('user'):
return gr.update(visible=True), gr.update(visible=True), gr.update(visible=True), gr.update(visible=False)
return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="Looks like you are not logged in. Please login at the main app.")
with gr.Blocks() as demo:
start_btn = gr.Button("Press Here to initialize the demo!")
with gr.Row():
inp = gr.Textbox(placeholder="What is your name?", visible=False)
out = gr.Textbox(visible=False)
btn = gr.Button("Run", visible=False)
start_btn.click(make_demo_visible, outputs=[inp, out, btn, start_btn])
btn.click(fn=update, inputs=inp, outputs=out)
gradio_app = gr.mount_gradio_app(app, demo, "/gradio")
app.add_middleware(SessionMiddleware, secret_key="!secret")
In the middleware you'll find these lines of code:
if request.url.path=='/gradio/api/predict' or request.url.path=='/gradio/reset':
return await call_next(request)
I included this because I found that when using this middleware, the fastapi makes POST request to /gradio/api/predict
and /gradio/reset
which messes up with gradio functionality, instead of making POST request over those, it has to make POST request on /api/predict
and /reset
to make gradio app function. I think developers need to work on routes.py, so that if someone makes multiple pathways to multiple gradio app they don't need to write condition statement for each app.
Please provide feedback on this solution and let me know if there's a better solution than this
@ambiSk Sorry for naive question, but how do you run the above block. When I run it, nothing stops and it just completes the code without effect.
@pseudotensor I understand you are not aware of how to launch a FastAPI app.
Install uvicorn
in your python environment using:
pip installl -U uvicorn
Then do either of the following:
- add this code line at the end of above script:
if __name__ == "__main__":
uvicorn.run(app)
When you run this file on CLI, you'll get domain to your tool
- Lets say the filename of this app is
main.py
, instead of adding the code from above, just run following command in your CLI. Make sure your CLI has opened in the directory wheremain.py
is:
uvicorn main:app
Hey! We've now made it possible for Gradio users to create their own custom components -- meaning that you can write some Python and JavaScript (Svelte), and publish it as a Gradio component. You can use it in your own Gradio apps, or share it so that anyone can use it in their Gradio apps. Here are some examples of custom Gradio components:
- A "Rich Textbox" that allows you to write bold/italics/colored text
- A "Folium Map Viewer" component that allows you to use interactive maps
You can see the source code for those components by clicking the "Files" icon and then clicking "src". The complete source code for the backend and frontend is visible. In particular, its very fast if you want to build off an existing component. We've put together a Guide: https://www.gradio.app/guides/five-minute-guide, and we're happy to help. Hopefully this will help address this issue.
@ambiSk
I can't get that to work as end-to-end example. So I have this code block as main.py:
import json
import uvicorn
from authlib.integrations.base_client import OAuthError
from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import HTMLResponse, RedirectResponse
from starlette.requests import Request
import gradio as gr
from authlib.integrations.starlette_client import OAuth
from starlette.config import Config
# CODE FOR NEW APP
app = FastAPI()
config = Config('.env')
oauth = OAuth(config)
CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration'
oauth.register(
name='google',
server_metadata_url=CONF_URL,
client_kwargs={
'scope': 'openid email profile'
}
)
# The Middleware that enforces authentication on /gradio app
@app.middleware("http")
async def check_authentication(request: Request, call_next):
if request.url.path.startswith('/login') or request.url.path.startswith('/auth'):
# Skip authentication check for login and authentication routes
return await call_next(request)
if request.url.path=='/gradio/api/predict' or request.url.path=='/gradio/reset':
return await call_next(request)
user = request.session.get("user")
if not user:
# User is not logged in, redirect to login page
return RedirectResponse(url="/login")
return await call_next(request)
@app.get('/')
async def homepage(request: Request):
user = request.session.get('user')
if user:
data = json.dumps(user)
html = (
f'<pre>{data}</pre>'
'<a href="/logout">logout</a>'
'<br>'
'<a href="/gradio">demo</a>'
)
return HTMLResponse(html)
return HTMLResponse('<a href="/login">login</a>')
@app.get('/login')
async def login(request: Request):
redirect_uri = request.url_for('auth')
return await oauth.google.authorize_redirect(request, redirect_uri)
@app.get('/auth')
async def auth(request: Request):
print(f"before request user {request.session.get('user')}")
try:
token = await oauth.google.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f'<h1>{error.error}</h1>')
user = token.get('userinfo')
if user:
request.session['user'] = dict(user)
print(f"after request user {request.session.get('user')}")
return RedirectResponse(url='/')
@app.get('/logout')
async def logout(request: Request):
request.session.pop('user', None)
return RedirectResponse(url='/')
# CODE FOR MOUNTED GRADIO APP
def update(name, request: gr.Request):
return f"Welcome to Gradio, {name}!\n{request.request.session.get('user')}"
def make_demo_visible(request: gr.Request):
if request.request.session.get('user'):
return gr.update(visible=True), gr.update(visible=True), gr.update(visible=True), gr.update(visible=False)
return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value="Looks like you are not logged in. Please login at the main app.")
with gr.Blocks() as demo:
start_btn = gr.Button("Press Here to initialize the demo!")
with gr.Row():
inp = gr.Textbox(placeholder="What is your name?", visible=False)
out = gr.Textbox(visible=False)
btn = gr.Button("Run", visible=False)
start_btn.click(make_demo_visible, outputs=[inp, out, btn, start_btn])
btn.click(fn=update, inputs=inp, outputs=out)
gradio_app = gr.mount_gradio_app(app, demo, "/gradio")
app.add_middleware(SessionMiddleware, secret_key="!secret")
and I run:
uvicorn main:app
As soon as I go to http://127.0.0.1:8000
it shows:
Sign in with Google
Access blocked: Authorization Error
[email protected]
The OAuth client was not found.
If you are a developer of this app, see [error details](https://accounts.google.com/).
Error 401: invalid_client
I removed my email as XXXX.
Do I need to fill .env file with something about who's authorized? I'm confused.
@pseudotensor Did you register your app in Google authentication service(I am not familiar to Google SSO, this is just a convenient reference to the product, not the product name)? Because the error message seems you passed a wrong client_id to Google, and Google refused to proceed.
Thanks, that helped. Now I'm stuck with my own app hanging after reaching loading (visual loading of gradio app with spinning candy looking thing) and unable to find style css files etc.
@abidlabs I am not able to understand how the custom component support will solve the OP's auth issue. I am in a similar situation as the OP. Perhaps it will help if you can post a hello world gradio code that uses firebase google auth and posts a custom message for the user.
Sorry for the late response @delip -- will work on something for this
For anyone who'd like to test this feature, I have a PR ready here: https://github.com/gradio-app/gradio/pull/7557
You can install this Gradio from this PR by doing:
pip install -q https://gradio-builds.s3.amazonaws.com/abf7bda7be2e2375e09514fde0ea8c86491900af/gradio-4.19.2-py3-none-any.whl
And you can add e.g. Google OAuth to your Gradio app like this:
import os
from authlib.integrations.starlette_client import OAuth, OAuthError
from fastapi import FastAPI, Depends, Request
from starlette.config import Config
from starlette.responses import RedirectResponse
from starlette.middleware.sessions import SessionMiddleware
import uvicorn
import gradio as gr
app = FastAPI()
# OAuth settings
GOOGLE_CLIENT_ID = "..."
GOOGLE_CLIENT_SECRET = "..."
SECRET_KEY = "..."
# Set up OAuth
config_data = {'GOOGLE_CLIENT_ID': GOOGLE_CLIENT_ID, 'GOOGLE_CLIENT_SECRET': GOOGLE_CLIENT_SECRET}
starlette_config = Config(environ=config_data)
oauth = OAuth(starlette_config)
oauth.register(
name='google',
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={'scope': 'openid email profile'},
)
SECRET_KEY = os.environ.get('SECRET_KEY') or "a_very_secret_key"
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
# Dependency to get the current user
def get_user(request: Request):
user = request.session.get('user')
if user:
return user['name']
return None
@app.get('/')
def public(user: dict = Depends(get_user)):
if user:
return RedirectResponse(url='/gradio')
else:
return RedirectResponse(url='/login-demo')
@app.route('/logout')
async def logout(request: Request):
request.session.pop('user', None)
return RedirectResponse(url='/')
@app.route('/login')
async def login(request: Request):
redirect_uri = request.url_for('auth')
return await oauth.google.authorize_redirect(request, redirect_uri)
@app.route('/auth')
async def auth(request: Request):
try:
access_token = await oauth.google.authorize_access_token(request)
except OAuthError:
return RedirectResponse(url='/')
request.session['user'] = dict(access_token)["userinfo"]
return RedirectResponse(url='/')
with gr.Blocks() as login_demo:
gr.Button("Login", link="/login")
app = gr.mount_gradio_app(app, login_demo, path="/login-demo")
def greet(request: gr.Request):
return f"Welcome to Gradio, {request.username}"
with gr.Blocks() as main_demo:
m = gr.Markdown("Welcome to Gradio!")
gr.Button("Logout", link="/logout")
main_demo.load(greet, None, m)
app = gr.mount_gradio_app(app, main_demo, path="/gradio", auth_dependency=get_user)
if __name__ == '__main__':
uvicorn.run(app)