title | tags | ||
---|---|---|---|
Injecting Unsupported OAuth Providers into Chainlit for Callback Support |
|
This repository provides a step-by-step guide to injecting unsupported OAuth providers into Chainlit for use in existing callback, authentication and logging functionality.
The app.py
in this repository only defines the @cl.oauth_callback decorator
and the prerequisite add_custom_oauth_provider
method. The rest of the app logic is intentionally left blank
I used Azure AD B2C for my custom provider, so my setup might look different than yours.
See the provided .py files for a direct reference
To get started with integrating your alternative OAuth provider with your Chainlit app, (loosely) follow these steps:
- Python ≥ 3.10
- A provisioned auth resource of your choosing
- Create your
foobar_provider.py
file - In that file, import the following:
import os
import httpx
from fastapi import HTTPException
from chainlit.user import User
from chainlit.oauth_providers import OAuthProvider
import json
- Set up your class and make sure it inherits from the Chainlit base class:
class FooBarProvider(OAuthProvider):
id="your-id"
env=["YOUR_ENV_VAR_NAMES"]
authorize_url=f"https://get.your/auth_url"
token_url=f"https://get.your/token_url"
def __init__(self):
self.client_id = os.environ.get("YOUR_ENV_VAR_NAMES")
async def get_token(self, code: str, url: str) -> str:
payload = { "foo": self.client_id }
async with httpx.AsyncClient() as client:
response = await client.post(
self.token_url,
data=payload
)
# do stuff, return a token
async def get_user_info(self, token: str):
async with httpx.AsyncClient() as client:
# do stuff with the token and return a user, User tuple
Keeping in line with how Chainlit supports OAuth providers like Github, Google, and Azure AD, we'll need to set up a few environment variables. For AADB2C, I used the following, but they may change based on need:
OAUTH_AZURE_AD_B2C_CLIENT_ID=your b2c application/client ID
OAUTH_AZURE_AD_B2C_CLIENT_SECRET=your b2c client secret
OAUTH_AZURE_AD_B2C_TENANT_ID=your b2c tenant id
OAUTH_AZURE_AD_B2C_TENANT_NAME=your b2c tenant name
OAUTH_AZURE_AD_B2C_REDIRECT_URL=your specified redirect url
OAUTH_AZURE_AD_B2C_POLICY=your policy
- Add each of the environment variables you create to the
env
property of your new provider class
For security reasons, I recommend validating the JWT returned from get_token. Modify the logic in validate_jwt.py
as needed. This should work out of the box for AADB2C.
Add another file called inject_custom_auth.py
and add the following, adjusting the env vars as needed:
import os
import secrets
import string
from chainlit.oauth_providers import providers
chars = string.ascii_letters + string.digits + "$%*,-./:=>?@^_~"
# This is the same as the Chainlit `random_secret` method
def random_secret(length: int = 64):
return "".join((secrets.choice(chars) for i in range(length)))
def custom_oauth_enabled():
if (os.environ.get('YOUR_ENV_VARS') is not None):
print("Custom OAuth configured.")
return True
else:
print("Custom OAuth not configured. Skipping...")
return False
def provider_id_in_instance_list(provider_id: str):
if providers is None:
print("No providers found")
return False
if not any(provider.id == provider_id for provider in providers):
print(f"Provider {provider_id} not found")
return False
else:
print(f"Provider {provider_id} found")
return True
# This ensures that the provider doesn't get added every time the application reloads.
def add_custom_oauth_provider(provider_id: str, custom_provider_instance):
if custom_oauth_enabled() and not provider_id_in_instance_list(provider_id):
providers.append(custom_provider_instance)
print(f"Added provider: {provider_id}")
else:
print(f"Custom OAuth is not enabled or provider {provider_id} already exists")
Add the necessary dependencies to your app.py
file and add a call to the add_custom_oauth_provider
method. The parameters for the method should be the id
specified in the provider class and an instance of the class.
i.e.
from cookbook.auth.foobar_oauthprovider import FooBarProvider
from cookbook.auth.inject_custom_auth import add_custom_oauth_provider
add_custom_oauth_provider("foobar", FooBarProvider())
@cl.oauth_callback
def oauth_callback(
provider_id: str,
token: str,
raw_user_data: Dict[str, str],
default_user: cl.User,
) -> Optional[cl.User]:
return default_user
The @cl.oauth_callback
should now work as normal with your new provider.
As of 03/13/2024, the injected provider is also fully supported in Literal.
The injected oauth provider doesn't look great on the login page, so I'd recommend overriding the styling on them and adding some JS handling. In your ../public
folder, add a stylesheet.css
and script.js
and provide them to your config.toml file as custom css and custom js files.
Start with the js and styles in the provided documentation, but feel free to customize as needed.