Table of Contents
Why is adding SSO important for these types of apps?
This tutorial was written by Kevin Kimani, a passionate developer and technical writer who enjoys explaining complicated concepts in a simple way. Connect with him on his GitHub or X to see more of his work!
Gradio is an open source Python package that allows you to create web-based interfaces for AI models, APIs, or any Python function. Its simplicity and flexibility make it a popular choice among developers who want to quickly prototype and deploy web-based interfaces without worrying about frontend development.
Secure authentication methods are needed for these applications to help prevent sensitive data and models from being exposed and ensure that only authorized users can access certain features. Descope is an authentication and user management platform that simplifies the process of adding secure authentication to your applications. It offers a range of authentication methods and out-of-the-box support for OAuth 2.0 and OpenID Connect (OIDC), which makes it easy to implement authentication in your Gradio app.
In this guide, you’ll learn how to integrate Descope authentication and single sign-on (SSO) into a Gradio application. You’ll add basic auth to your Gradio application using magic links and social login. You’ll also implement SSO into the application by configuring Descope to use Okta as an identity provider(IdP).
Why is adding SSO important for these types of apps?
Many Gradio applications are used for business-to-business (B2B) purposes, where different organizations need secure access to their machine-learning models or APIs. In such cases, SSO is essential for several reasons:
Seamless access: Employees can log in using their company credentials instead of managing separate usernames and passwords.
Improved security: SSO reduces password fatigue and minimizes security risks associated with weak or reused passwords.
Better user management: IT administrators can enforce access policies, control permissions, and revoke access centrally through identity providers like Okta or Azure AD.
Prerequisites
To follow this tutorial, you need the following:
Python 3 installed on your local machine
Git CLI installed on your local machine
A code editor and a web browser
Creating a Gradio application
To keep the focus on implementing authentication and SSO, the tutorial uses a prepared starter template that you’ll build on. To clone it to your local machine, execute the command below:
git clone --single-branch -b starter-template https://github.com/kimanikevin254/descope-gradio-auth-sso.git
The Gradio application you just cloned is mounted within a FastAPI application. This setup is necessary because Gradio only supports authentication with external OAuth providers, such as Descope, when it is mounted inside a FastAPI app.
Here’s an overview of the most important files in this project:
app/core/config.py
: Loads environment variables and sets application configurations using Pydantic.app/ui/gradio_apps/
: Contains three simple Gradio applications:admin_dashboard.py
: Displays user info and a logout button. In a real-world app, this would handle admin-specific features.user_dashboard.py
: Similar to the admin dashboard but for regular users. This is where you would implement non-admin functionalities.login_page.py
: A simple login page that prompts users to log in with a login button.
app/ui/gradio_mount.py
: Defines the GradioMounter class, which mounts all Gradio apps to the FastAPI application..env.example
: Defines the environment variables you will require in this project.
You’ll explore the other files later.
With the files cloned to your local machine, you can move on to setting up the application. Start by creating a virtual environment, activating it, and installing all the dependencies:
python3 -m venv .venv # Create the virtual environment
source .venv/bin/activate #Activate it
pip install -r requirements.txt # Install dependencies
Rename the .env.example
file to .env
:
mv .env.example .env
Run the application using the command fastapi dev app/main.py and navigate to http://localhost:8000/auth/
to view the login page created using Gradio:

To view the admin dashboard, navigate to http://localhost:8000/gradio/admin
:

To view the user dashboard, navigate to http://localhost:8000/gradio/user:

As you can see, all the dashboards are publicly available, which poses some security risks. In the next sections, you’ll add authentication to protect them and authorization to make sure that only users with the appropriate role can access the admin dashboard.
Setting up Descope
To integrate Descope as an external OAuth provider for your Gradio application, you’ll need to apply some configurations in your Descope console.
Open your Descope console and create a new project by clicking the project dropdown and selecting + Project:

Provide a project name on the Create project form and click Create:

You need to define a flow that will support the authentication methods you require for this project: magic links, social login, and SSO. To simplify the process, this tutorial uses a preconfigured flow included in the starter template. You’ll find it in the root folder as sign-up-or-in.json
. You only need to import it into Descope. To do this, navigate to Flows > sign-up-or-in and select Import flow / Export flow > Import flow inside the flow editor. This will allow you to upload the flow from your local machine:

The flow you just imported offers three login options on the welcome screen: magic link, SSO, and social login. If a user chooses the magic link, they receive an email with a link. Clicking the link prompts new users to provide extra details while existing users get a JWT and complete the process. For social login or SSO, users are redirected to their provider, and after successful authentication, they receive a JWT, ending the flow.
Authenticating with Descope
With Descope fully set up, you can now integrate it as a custom OAuth provider for your Gradio application. You’ll need to configure an OIDC app in the Descope dashboard to obtain the necessary endpoints and credentials for the authorization code flow.
Here are the required credentials:
Client ID: A unique public identifier assigned to the application
Client secret: A confidential key shared only between the application and the authorization server
Authorization endpoint: The URL that starts the authentication process
Access token endpoint: The URL used to exchange an authorization code for access tokens
User info endpoint: The URL that retrieves details about the authenticated user
Redirect URL: The destination where users are sent after completing authentication
JWKs URI: The URL where the authorization server’s public keys are stored for verifying tokens
Scopes: Permissions that define what data and actions the application can access on behalf of the user
You will define these in the .env
file. Some values for the variables are already included in the starter template, as are common for everyone. To get the ones that are not provided, navigate to Applications > OIDC default application on your Descope console. Scroll down to the SP Configuration section, copy the value of the Client ID, and assign it to the OAUTH_CLIENT_ID
variable in the .env
file:

Make sure you also replace the placeholder inside the OAUTH_JWKS_URI
's value with the same client ID.
The OAUTH_SCOPES
variable in the .env
file specifies the scopes your application will request from Descope. However, Descope does not include the descope.claims
(user’s roles, permissions, and tenants) and descope.custom_claims
(user’s custom claims) scopes in its response by default unless explicitly configured.
To enable these scopes, go to the OIDC default application details page. In the IDP Configuration section, add descope.claims
and descope.custom_claims
under Supported Claims. Make sure to save the changes:

To obtain the value for the OAUTH_CLIENT_SECRET
variable, navigate to M2M > + Access Key on your Descope console. On the Generate Access Key page, provide a name for your key and select Generate Key. Copy the value of the generated key and assign it to the OAUTH_CLIENT_SECRET
variable in the .env
file:

You now have all the required values for implementing the authentication logic. Open the app/core/auth.py
file and add the following method to the Auth
class to initialize the OAuth client with the necessary settings:
# Initialize the OAuth client with settings
def init_oauth(self, settings):
self.settings = settings
self.oauth.register(
name=self.settings.OAUTH_CLIENT_NAME,
client_id=self.settings.OAUTH_CLIENT_ID,
client_secret=self.settings.OAUTH_CLIENT_SECRET,
authorize_url=self.settings.OAUTH_AUTHORIZE_URL,
access_token_url=self.settings.OAUTH_ACCESS_TOKEN_URL,
redirect_uri=self.settings.OAUTH_REDIRECT_URI,
jwks_uri=self.settings.OAUTH_JWKS_URI,
userinfo_endpoint=self.settings.OAUTH_USERINFO_ENDPOINT,
client_kwargs={'scope': self.settings.OAUTH_SCOPES},
)
Add the following methods to the same class. These methods will redirect the user to the authorization endpoint set up for the OAuth client and exchange the returned authorization code for an access token:
# Redirect to authorization endpoint
async def authorize_redirect(self, request: Request, redirect_uri: str):
return await getattr(self.oauth, self.settings.OAUTH_CLIENT_NAME).authorize_redirect(request, redirect_uri)
# Exchange authorization code for access token
async def authorize_access_token(self, request: Request):
try:
return await getattr(self.oauth, self.settings.OAUTH_CLIENT_NAME).authorize_access_token(request)
except OAuthError as error:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"OAuth error: {error.error}"
)
Add the following method to the same class to retrieve the currently authenticated user from the session:
# Get the current authenticated user name
def get_current_user(self, request: Request) -> Optional[str]:
user = request.session.get("user")
if user:
return user['email']
This method helps check if a request is authenticated when accessing API routes. If the user is not authenticated, you can take appropriate action, such as redirecting them to the login page.
You’ll also need a method to protect Gradio apps by ensuring the user is authenticated and has the necessary roles. Add the following method to the Auth
class:
# Authenticate user and authorize based on path and roles
# To protect Gradio app routes
def authenticate_and_authorize(self, request: Request) -> Optional[str]:
path = request.url.path
user = request.session.get("user", {})
tenants = user.get('tenants', [])
roles = set()
if isinstance(tenants, dict):
for data in tenants.values():
roles.update(data.get('roles', []))
# Avoid blocking the gradio queue requests
if '/gradio_api/queue' in path and user:
return user['email']
if user and self.settings.ADMIN_ROLE in roles and path == self.settings.ADMIN_DASHBOARD_PATH:
print('pass. returning', user['email'])
return user['email']
elif user and path == self.settings.USER_DASHBOARD_PATH:
return user['email']
else:
return None
Add the following method to the Auth
class to determine which Gradio app an authenticated user should see based on their roles:
# Determine the Gradio app to show the user based on their roles
def get_user_redirect_path(self, request: Request) -> str:
user = request.session.get("user", {})
tenants = user.get('tenants', [])
roles = set()
if isinstance(tenants, dict):
for data in tenants.values():
roles.update(data.get('roles', []))
else:
print('You have not set up tenants in your Descope account. You can only access the user dashboard since the roles needed to access the admin dashboard are set up via the tenant.')
if not user:
return self.settings.LOGIN_PAGE_PATH
if self.settings.ADMIN_ROLE in roles:
return self.settings.ADMIN_DASHBOARD_PATH
else:
return self.settings.USER_DASHBOARD_PATH
To ensure the OAuth client is correctly initialized when the application starts, open the app/main.py
file and add the following code immediately after the middleware configuration:
# Initialize OAuth client
auth.init_oauth(settings)
Set up all the auth routes by adding the code to the app/api/routes/auth_router.py
file:
# Redirect to OAuth login provider
@router.get('/login')
async def login(request: Request):
redirect_uri = request.url_for('auth_callback')
return await auth.authorize_redirect(request, redirect_uri)
# Handle OAuth callback after successful login
@router.get("/auth/callback", name="auth_callback")
async def auth_callback(request: Request):
try:
token = await auth.authorize_access_token(request)
user = token.get("userinfo")
request.session["user"] = dict(user)
return RedirectResponse(url="/", status_code=HTTP_302_FOUND)
except HTTPException as e:
# Log the error
print(f"Authentication error: {e.detail}")
return RedirectResponse(url="/auth/error", status_code=HTTP_302_FOUND)
# Log out the current user
@router.get('/logout')
async def logout(request: Request):
auth.logout(request)
return RedirectResponse(url="/", status_code=HTTP_302_FOUND)
# Authentication error page
@router.get("/error")
async def auth_error():
return {"error": "Authentication failed. Please try again."}
This code defines routes that allow the user to log in, handle the OAuth callback, and log out of the application.
Set up the dashboard routes that will display the appropriate dashboards to the user based on their roles by replacing the existing route in the app/api/routes/dashboard_router.py
file with the following:
# Main entry point, redirects based on user role
@router.get("/")
def index(request: Request):
user = auth.get_current_user(request)
if user:
redirect_path = auth.get_user_redirect_path(request)
return RedirectResponse(url=redirect_path, status_code=HTTP_302_FOUND)
else:
return RedirectResponse(url=auth.settings.LOGIN_PAGE_PATH, status_code=HTTP_302_FOUND)
# Routing endpoint for Gradio dashboards
@router.get("/gradio")
async def route_dashboard(request: Request):
redirect_path = auth.get_user_redirect_path(request)
return RedirectResponse(url=redirect_path, status_code=HTTP_302_FOUND)
Lastly, protect the Gradio dashboards by replacing the mount_admin_dashboard
and mount_user_dashboard
methods in the app/ui/gradio_mount.py
file with the following:
# Mount the admin dashboard with authorization
def mount_admin_dashboard(self):
with gr.Blocks() as admin_dashboard_wrapper:
admin_dashboard.main.render()
self.app = gr.mount_gradio_app(
self.app,
admin_dashboard_wrapper,
path=self.settings.ADMIN_DASHBOARD_PATH,
auth_dependency=self.auth.authenticate_and_authorize
)
# Mount the user dashboard with authorization
def mount_user_dashboard(self):
with gr.Blocks() as user_dashboard_wrapper:
user_dashboard.main.render()
self.app = gr.mount_gradio_app(
self.app,
user_dashboard_wrapper,
path=self.settings.USER_DASHBOARD_PATH,
auth_dependency=self.auth.authenticate_and_authorize
)
This code adds the auth_dependency
parameter before mounting these apps, which runs before any Gradio-related route in your FastAPI app. This ensures that proper authentication and authorization checks are performed before displaying the dashboards.
You can now log in to the application using either magic links or social login.
Implementing SSO with OIDC using Descope
OIDC is an identity layer built on top of the OAuth 2.0 framework. It allows applications to authenticate users securely by delegating authentication to a trusted IdP, such as Okta. OIDC simplifies SSO by enabling users to log in once and access multiple applications without reentering credentials.
To implement SSO, you can configure Okta as the IdP and Descope as the authentication service. Start by launching your Okta admin dashboard and navigating to Applications > Applications > Browse App Catalog:

On the Browse App Catalog page, search for descope
, and select Descope from the search results:

Select + Add Integration from the Descope app details page:

On the General Settings tab, leave everything as is and click Next:

On the Sign-On options tab, select OpenID Connect as the sign-on method:

Scroll down to the Advanced Sign-on Settings section, and in the Callback URL field, provide the value https://api.descope.com/v1/oauth/callback
. This defines the URL where the user will be redirected after getting successfully authenticated by Okta. Save the changes by clicking Done at the bottom of the page:

You also need to assign users to the Descope app you just configured. This will ensure that only authorized users can authenticate via the app. To do this, go to the app’s details page and the Assignments tab and select Assign > Assign to Groups:

On the Assign Descope to Groups page, select the Assign button beside the Everyone group to allow all users in your organization to authenticate via the app, and select Done to save the changes.

Select the Sign On tab and take note of your OIDC client ID and secret:

Go to the Descope console to create a tenant that you will use with the Descope app you just configured in Okta. On your Descope console, navigate to Tenants > + Tenant, provide the tenant details on the Create Tenant form, and select Create:

Open the details page of the Tenant you just created, add your email domain under the Tenant Settings > Details section in the Email domain field, and save the changes:

Select Authentication Methods > SSO from the tenant details page sidebar, and under Authentication Protocol, select OIDC:

Under Tenant Details > SSO Domains, provide your email domain. This will help to determine which SSO configuration to load once a user chooses to authenticate using SSO:

Scroll down to the SSO configuration > Account Settings section and provide the required values as follows:
Provider Name:
Okta
Client ID: The Client ID you obtained from the Okta dashboard
Client Secret: The client secret you obtained from the Okta dashboard
Scope:
openid profile email
Grant Type:
Authorization code
To obtain the values needed for the SSO configuration > Connection Settings section, you’ll need the OAuth endpoints from the Okta “well-known” configuration. Navigate to https://<YOUR-OKTA-INSTANCE>.okta.com/.well-known/openid-configuration
on your browser, and take note of the issuer and authorization, token, user info, and JWKs endpoints.
Make sure to replace <YOUR-OKTA-INSTANCE>
with the correct value, which is your organization’s Okta instance ID.

Head back to the Descope console, provide these values in the respective inputs under SSO configuration > Connection Settings, and save the changes.

SSO with Okta as the IdP is now fully configured.
Demonstrating the application
To run the application and confirm that everything is working as expected, use the command fastapi dev app/main.py
and navigate to http://localhost:8000
on your browser. You will be redirected to the login page:

Click the Login button, and you’ll be redirected to Descope’s sign-in page, which is powered by the flow you configured earlier. You can sign in using any of the available methods:

After successful authentication, you’ll be redirected to the user dashboard. This is because you don’t have the appropriate roles to access the admin dashboard:

If you manually tried to navigate to http://localhost:8000/gradio/admin
, you will get an error informing you that you’re not authenticated:

To check if a user with the appropriate role can access the admin dashboard, open the users’ page on the Descope console, edit your user to assign them the Tenant Admin
role, and save the changes:

Now, go back to the app, log out, and log in again. You should be redirected to the admin dashboard:

Conclusion
In this guide, you learned how to integrate Descope as an OAuth provider for your Gradio application using OIDC. You also explored how to implement SSO with Okta as the identity provider and Descope as the authentication service. Additionally, you implemented role-based access control to protect Gradio app routes.
Descope is a drag-and-drop customer authentication and identity management platform. Our no- or low-code CIAM solution helps hundreds of organizations easily create and customize their entire user journey using visual workflows—from authentication and authorization to MFA and federated SSO. Customers such as GoFundMe, Navan, You.com, and Branch use Descope to reduce user friction, prevent account takeover, and get a unified view of their customer journey.
To learn more, join our dev community, AuthTown, and explore the Descope documentation.