OAuth

Basic scaffolding for handling OAuth

See the docs page for an explanation of how to use this.

from IPython.display import Markdown

GoogleAppClient

def GoogleAppClient(
    client_id, client_secret, code:NoneType=None, scope:NoneType=None, project_id:NoneType=None, **kwargs
):

A WebApplicationClient for Google oauth2


GitHubAppClient

def GitHubAppClient(
    client_id, client_secret, code:NoneType=None, scope:NoneType=None, **kwargs
):

A WebApplicationClient for GitHub oauth2


HuggingFaceClient

def HuggingFaceClient(
    client_id, client_secret, code:NoneType=None, scope:NoneType=None, state:NoneType=None, **kwargs
):

A WebApplicationClient for HuggingFace oauth2


DiscordAppClient

def DiscordAppClient(
    client_id, client_secret, is_user:bool=False, perms:int=0, scope:NoneType=None, **kwargs
):

A WebApplicationClient for Discord oauth2


Auth0AppClient

def Auth0AppClient(
    domain, client_id, client_secret, code:NoneType=None, scope:NoneType=None, redirect_uri:str='', **kwargs
):

A WebApplicationClient for Auth0 OAuth2

Apple Sign In requires a few extra steps compared to other OAuth providers:

  1. Client secret is a JWT — Instead of a static secret, you generate a signed JWT using a private key from Apple
  2. Callback is POST, not GET — Apple sends the authorization code via form POST, so you need redir_method='post'
  3. User info is in the ID token — There’s no separate userinfo endpoint; you decode the JWT they return

See Apple’s Sign In documentation for details.

To use Apple Sign In, you’ll need credentials from the Apple Developer Portal:

  1. Team ID — Found in your Membership section
  2. App ID — Create under Identifiers → App IDs with “Sign in with Apple” capability enabled
  3. Service ID — Create under Identifiers → Services IDs, linked to your App ID (this becomes your client_id)
  4. Key ID — Create under Keys with “Sign in with Apple” enabled
  5. Private Key (.p8 file) — Downloaded when you create the key (you can only download it once!)

When configuring your Service ID, you’ll need to specify your domain and return URL (e.g., https://yourdomain.com/auth/callback). Note: Apple doesn’t accept localhost — use a real domain or a tunneling service like ngrok for local development. (I used a solveit public domain for easy prototyping)


AppleAppClient

def AppleAppClient(
    client_id, key_id, team_id, private_key, code:NoneType=None, scope:NoneType=None, **kwargs
):

A WebApplicationClient for Apple Sign In

# cli = GoogleAppClient.from_file('client_secret.json')

Generating a login link that sends the user to the OAuth provider is done with client.login_link().

It can sometimes be useful to pass state to the OAuth provider, so that when the user returns you can pick up where they left off. This can be done by passing the state parameter.

from fasthtml.jupyter import *
redir_path = '/redirect'
port = 8000
# app,rt = fast_app()
# server = JupyUvi(app, port=port)

get_host

def get_host(
    request
):

Get the host, preferring X-Forwarded-Host if available

from types import SimpleNamespace
from urllib.parse import urlparse
mock_request_localhost = SimpleNamespace(headers={}, url=SimpleNamespace(netloc='localhost:8000'))
mock_request_with_forward = SimpleNamespace(
    headers={'x-forwarded-host': 'example.com'}, 
    url=SimpleNamespace(netloc='localhost:8000', hostname='localhost')
)

print("Without X-Forwarded-Host:", get_host(mock_request_localhost))
print("With X-Forwarded-Host:", get_host(mock_request_with_forward))
Without X-Forwarded-Host: localhost:8000
With X-Forwarded-Host: example.com

redir_url

def redir_url(
    req, redir_path, scheme:NoneType=None
):

Get the redir url for the host in request

@rt
def index(request):
    redir = redir_url(request, redir_path)
    return A('login', href=cli.login_link(redir), target='_blank')

_AppClient.retr_info

def retr_info(
    code, redirect_uri
):

Combines parse_response and get_info


_AppClient.get_info

def get_info(
    token:NoneType=None
):

Get the info for authenticated user


_AppClient.parse_response

def parse_response(
    code, redirect_uri
):

Get the token from the oauth2 server response


_AppClient.retr_info_async

async def retr_info_async(
    code, redirect_uri
):

Combines parse_response and get_info


_AppClient.get_info_async

async def get_info_async(
    token:NoneType=None
):

Get the info for authenticated user


_AppClient.parse_response_async

async def parse_response_async(
    code, redirect_uri
):

Get the token from the oauth2 server response

@rt(redir_path)
def get(request, code:str):
    redir = redir_url(request, redir_path)
    info = cli.retr_info(code, redir)
    return P(f'Login successful for {info["name"]}!')
# HTMX()
# server.stop()

_AppClient.retr_id

def retr_id(
    code, redirect_uri
):

Call retr_info and then return id/subscriber value

After logging in via the provider, the user will be redirected back to the supplied redirect URL. The request to this URL will contain a code parameter, which is used to get an access token and fetch the user’s profile information. See the explanation here for a worked example. You can either:

After either of these calls, you can also access the access token (used to revoke access, for example) with client.token["access_token"].


url_match

def url_match(
    request, patterns:tuple=('^(localhost|127\\.0\\.0\\.1)(:\\d+)?$',)
):
mock_request_prod = SimpleNamespace(headers={}, url=SimpleNamespace(netloc='myapp.com', hostname='myapp.com'))

print("Localhost:", redir_url(mock_request_localhost, '/redirect'))
print("With X-Forwarded-Host:", redir_url(mock_request_with_forward, '/redirect'))
print("Production:", redir_url(mock_request_prod, '/redirect'))
Localhost: http://localhost:8000/redirect
With X-Forwarded-Host: https://example.com/redirect
Production: https://myapp.com/redirect

OAuth

def OAuth(
    app, cli, skip:NoneType=None, redir_path:str='/redirect', error_path:str='/error', logout_path:str='/logout',
    login_path:str='/login', https:bool=True, http_patterns:tuple=('^(localhost|127\\.0\\.0\\.1)(:\\d+)?$',),
    redir_method:str='get'
):

Google helpers


GoogleAppClient.consent_url

def consent_url(
    proj:NoneType=None
):

Get Google OAuth consent screen URL


GoogleAppClient.consent_url

def consent_url(
    proj:NoneType=None
):

Get Google OAuth consent screen URL


Credentials.update

def update():

Refresh the credentials if they are expired, and return them


Credentials.update

def update():

Refresh the credentials if they are expired, and return them


Credentials.save

def save(
    fname
):

Save credentials to fname


Credentials.save

def save(
    fname
):

Save credentials to fname


load_creds

def load_creds(
    fname
):

Load credentials from fname


GoogleAppClient.creds

def creds():

Create Credentials from the client, refreshing if needed


GoogleAppClient.creds

def creds():

Create Credentials from the client, refreshing if needed