Gradio Authentication

Gradio is a Python library that enables developers to rapidly create intuitive web interfaces for machine learning models and data science workflows with minimal code. It offers a simple and intuitive way to create user-friendly demos and applications, allowing users to interact with and test models directly in a web browser without requiring any front-end development knowledge.

That being said, out-of-the-box authentication options with Gradio are limited. That's where PropelAuth comes in. This guide will cover how to build and deploy a Gradio app using PropelAuth for authentication. We'll be using PropelAuth's OAuth2 support alongside Gradio's OAuth support with external providers.

Setup in PropelAuth

We first need to create an OAuth2 provider within PropelAuth. To do so, navigate to the Frontend Integration page in your PropelAuth dashboard, click on Advanced Settings then Edit OAuth Config.

Navigating to OAuth2 Dashboard Page

Create a New OAuth Client and copy down your Client ID and Client Secret. We'll be needing these for later.

While we're here, let's also add a Redirect URI. In this guide we'll be using http://localhost:8000/auth/callback.

OAuth2 Dashboard Page

The last variable we need is our Auth URL. You can find this back in the Frontend Integration page.

Installation

In your Gradio project, install the propelauth-py library:

pip install propelauth_py

Now create a new file in your root directory and name it propelauth.py. Copy the following code into it, making sure to edit YOUR_AUTH_URL and YOUR_API_KEY with the values found in your Backend Integration page in PropelAuth. You likely want to load them as environment variables instead of hardcoding them.

propelauth.py

from propelauth_py import init_base_auth, UnauthorizedException, User
from fastapi import Request, Response
import gradio as gr

# Replace me
AUTH_URL = "{YOUR_AUTH_URL}"
API_KEY = "{YOUR_API_KEY}"

class Auth:
    def __init__(self, auth_url, integration_api_key):
        self.auth = init_base_auth(auth_url, integration_api_key)
        self.auth_url = auth_url
        self.integration_api_key = integration_api_key
        self.js_logout = """
        () => {
            const requestOptions = {
                method: "POST",
            };
            fetch("/auth/logout", requestOptions)
                .then(response => response.text())
                .then(() => window.location.href = "/")
        }
        """

    def get_user_from_access_token(self, user, request: Request):
        try:
            if request.session.get('access_token') is None:
                return self.force_refresh_user(user, request)
            return self.auth.validate_access_token_and_get_user(f"Bearer {request.session['access_token']}")
        except UnauthorizedException:
            return self.force_refresh_user(user, request)
            
    def force_refresh_user(self, user_id, request):
        access_token_response = self.auth.create_access_token(user_id, 10)
        request.session['access_token'] = access_token_response.access_token
        return self.auth.validate_access_token_and_get_user(f"Bearer {request.session['access_token']}")
    
    def get_account_url(self):
        return self.auth_url + "/account"
    
    def log_out(self, request: Request):
        if request.session:
            user = request.session.get('user')
            if user is not None:
                self.auth.logout_all_user_sessions(user['sub'])
            request.session.clear()
        return Response()
        
    def get_user_dependency(self, request: Request):
        user = request.session.get('user')
        if user:
            propelauth_user = self.get_user_from_access_token(user['sub'], request)
            request.state.user = propelauth_user
            return user['sub']
        return None
    
    def get_user(self, request: gr.Request) -> User:
        return request.state.user

auth = Auth(
    AUTH_URL,
    API_KEY
)

This is a helper class which will help retrieve the user's information, redirect to the user's hosted account page, and log the user out. We'll cover some of these functions in this guide, but feel free to use these functions or add more yourself!

Configuration

To authenticate with PropelAuth in your Gradio app, you must mount your app within a FastAPI app. This will allow us to create routes with FastAPI to handle the authentication process.

In your Gradio app's main file, use the Auth URL, Client ID, and Client Secret that we retrieved in the previous step. You likely want to load them as environment variables instead of hardcoding them. You'll also need to create a SECRET_KEY which is a randomly generated string.

We'll be using Starlette's SessionMiddleware to handle the user's session. In the next step, we'll add some custom middleware to automatically redirect the user to login if they're not authenticated.

app.py

from authlib.integrations.starlette_client import OAuth
from fastapi import FastAPI
from starlette.config import Config
from starlette.middleware.sessions import SessionMiddleware

PROPELAUTH_CLIENT_ID = "{YOUR_CLIENT_ID}"
PROPELAUTH_CLIENT_SECRET = "{YOUR_CLIENT_SECRET}"
PROPELAUTH_AUTH_URL = "{YOUR_AUTH_URL}"
SECRET_KEY = "{RANDOMLY_GENERATED_STRING}"

config_data = {
    'PROPELAUTH_CLIENT_ID': PROPELAUTH_CLIENT_ID, 
    'PROPELAUTH_CLIENT_SECRET': PROPELAUTH_CLIENT_SECRET
}
starlette_config = Config(environ=config_data)
oauth = OAuth(starlette_config)
oauth.register(
    name='propelauth',
    server_metadata_url=f'{PROPELAUTH_AUTH_URL}/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid email profile'},
)

app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY, https_only=True)

Next, let's add some routes that will handle the login and logout process. We'll also create some middleware that will automatically redirect the user to login if SessionMiddleware returns a 401.

app.py

from authlib.integrations.starlette_client import OAuth, OAuthError
from fastapi import FastAPI, Request
from starlette.responses import RedirectResponse
from propelauth import auth

@app.middleware("http")
async def postprocess_middleware(request, call_next):
    response = await call_next(request)
    if response.status_code == 401:
        return RedirectResponse("/auth/login")
    else:
        return response

@app.route('/auth/login')
async def handle_login(request: Request):
    redirect_uri = request.url_for('callback')
    if oauth.propelauth:
        # redirects user to login via PropelAuth
        return await oauth.propelauth.authorize_redirect(request, redirect_uri)
    else:
        return "Authentication not configured"

# The Redirect URI we set in PropelAuth
@app.route('/auth/callback')
async def callback(request: Request):
    try:
        if oauth.propelauth:
            access_token = await oauth.propelauth.authorize_access_token(request)
        else:
            return None
    except OAuthError as e:
        return f"An OAuth error has occurred during callback: {e}"
    request.session['user'] = dict(access_token)["userinfo"]
    return RedirectResponse(url='/')

@app.route('/auth/logout', methods=["POST"])
async def logout(request: Request):
    return auth.log_out(request)

And we're all setup! If you were to navigate to your application you will be automatically be redirected to login. Your app is now protected by PropelAuth!

Displaying User information

Getting the logged in user's information couldn't be simpler. Add the get_user_dependency function from our propelauth.py file as the auth_dependency when mounting the Gradio app. Then, simply pass the Gradio Request into the get_user function and that's it! If the user is logged in, we'll get back a User object.

import gradio as gr
from propelauth import auth

def welcome(request: gr.Request):
    user = auth.get_user(request)
    return f"Welcome to Gradio, {user.email}!"

with gr.Blocks() as main:
    m = gr.Markdown("")
    main.load(welcome, None, m)

app = gr.mount_gradio_app(app, main, path="", auth_dependency=auth.get_user_dependency)

Authorization / organizations

You can also verify which organizations the user is in, and which roles and permissions they have in each organization using the User object.

Check Org Membership

Verify that the user is logged in and that the user is a member of the specified organization.

def get_user_orgs(request: gr.Request):
    propelauth_user = auth.get_user(request)
    orgs = propelauth_user.get_orgs()
    org_choices = [(org.org_name, org.org_id) for org in orgs]
    org_dropdown = gr.Dropdown(label="Select Organization", choices=org_choices, value=org_choices[0][1] if org_choices else None, interactive=True)
    return org_dropdown

def check_membership(org_id, request: gr.Request):
    propelauth_user = auth.get_user(request)
    org = propelauth_user.get_org(org_id)
    if org:
        return f"You are in org {org.org_name}"
    else:
        return "You do not belong to this org."

with gr.Blocks() as main:
    org_dropdown = gr.Dropdown(label="Select Organization", choices=[], interactive=True)
    org_info = gr.Markdown("")
    org_dropdown.change(fn=check_membership, inputs=[org_dropdown], outputs=org_info)
    main.load(get_user_orgs, None, [org_dropdown])

app = gr.mount_gradio_app(app, main, path="", auth_dependency=auth.get_user_dependency)

Check Org Membership and Role

Similar to checking org membership, but will also verify that the user has a specific Role in the organization. This can be done using either the User or OrgMemberInfo objects.

A user has a Role within an organization. By default, the available roles are Owner, Admin, or Member, but these can be configured. These roles are also hierarchical, so Owner > Admin > Member.

def get_user_orgs(request: gr.Request):
    propelauth_user = auth.get_user(request)
    orgs = propelauth_user.get_orgs()
    org_choices = [(org.org_name, org.org_id) for org in orgs]
    org_dropdown = gr.Dropdown(label="Select Organization", choices=org_choices, value=org_choices[0][1] if org_choices else None, interactive=True)
    return org_dropdown

def check_role(org_id, request: gr.Request):
    propelauth_user = auth.get_user(request)
    org = propelauth_user.get_org(org_id)
    role_to_check = "Admin"
    if org and org.user_is_role(role_to_check):
        return f"You are an Admin in org {org.org_name}"
    else:
        return "You must be an Admin to access this page."

with gr.Blocks() as main:
    org_dropdown = gr.Dropdown(label="Select Organization", choices=[], interactive=True)
    org_info = gr.Markdown("")
    org_dropdown.change(fn=check_role, inputs=[org_dropdown], outputs=org_info)
    main.load(get_user_orgs, None, [org_dropdown])

app = gr.mount_gradio_app(app, main, path="", auth_dependency=auth.get_user_dependency)

Check Org Membership and Permission

Similar to checking org membership, but will also verify that the user has the specified permission in the organization. This can be done using either the User or OrgMemberInfo objects.

Permissions are arbitrary strings associated with a role. For example, can_view_billing, ProductA::CanCreate, and ReadOnly are all valid permissions. You can create these permissions in the PropelAuth dashboard.

def get_user_orgs(request: gr.Request):
    propelauth_user = auth.get_user(request)
    orgs = propelauth_user.get_orgs()
    org_choices = [(org.org_name, org.org_id) for org in orgs]
    org_dropdown = gr.Dropdown(label="Select Organization", choices=org_choices, value=org_choices[0][1] if org_choices else None, interactive=True)
    return org_dropdown

def check_permission(org_id, request: gr.Request):
    propelauth_user = auth.get_user(request)
    org = propelauth_user.get_org(org_id)
    permission_to_check = "can_view_billing"
    if org and org.user_has_permission(permission_to_check):
        return f"You can view billing information for org {org.org_name}"
    else:
        return "You do not have the necessary permission to view billing information."

with gr.Blocks() as main:
    org_dropdown = gr.Dropdown(label="Select Organization", choices=[], interactive=True)
    org_info = gr.Markdown("")
    org_dropdown.change(fn=check_permission, inputs=[org_dropdown], outputs=org_info)
    main.load(get_user_orgs, None, [org_dropdown])

app = gr.mount_gradio_app(app, main, path="", auth_dependency=auth.get_user_dependency)

Calling Backend APIs

As you may have noticed, this guide uses PropelAuth's propelauth-py library to retrieve the user's information, among other things. This means you can use the propelauth-py library to make requests to PropelAuth's Backend API, allowing you to fetch members of an organization, create orgs, and a lot more.

from propelauth import auth

magic_link = auth.auth.create_magic_link(email="test@example.com")

Logging Out

To log a user out of your Gradio app we will use the js event parameter on a button to have the client make a request to the /auth/logout route that we created earlier. This will clear the user's session and log them out of your Gradio app. If you want to change where the user is redirected to after logging out, you can do so by changing the js_logout script in the propelauth.py file.

import gradio as gr

logout_button = gr.Button("Logout")
logout_button.click(None, None, None, js=auth.js_logout)

When clicked, the client will make a POST request to the /auth/logout route where they will be logged out of your Gradio app as well as PropelAuth's hosted pages.

Logging Out Through The Hosted Pages

If you're using PropelAuth's hosted pages you may find that the Log Out button found in the Account Page Sidebar does not completely log the user out of their Gradio session.

Hosted pages logout button

Removing the Log Out Button

There are a couple of ways to address this. First, you can remove the Sidebar by navigating to the Look & Feel section of your PropelAuth Dashboard, clicking Open Editor followed by Management Pages towards the top.

Find the Display Sidebar setting and disable it.

Disable hosted pages sidebar

The Sidebar as well as the Logout button are now removed from the hosted pages.

Have any questions? Please reach out to support@propelauth.com.