The fastest way to create HTML apps - a next-generation Python web framework for building fast, scalable web applications with minimal code
—
OAuth integration with multiple providers, basic authentication middleware, and session management for secure web applications.
Complete OAuth authentication system with support for multiple providers including Google, GitHub, Discord, Hugging Face, and Auth0.
class OAuth:
"""
Main OAuth handler class.
Manages OAuth authentication flow including authorization,
token exchange, and user information retrieval.
"""
def __init__(self, app, client, redirect_uri: str, logout_uri: str = '/logout'):
"""
Initialize OAuth handler.
Args:
app: FastHTML application instance
client: OAuth client (GoogleAppClient, GitHubAppClient, etc.)
redirect_uri: URI to redirect after authentication
logout_uri: URI for logout functionality
"""
def login_link(self, info: str = None, **scope) -> str:
"""Generate OAuth login URL."""
def logout_link(self) -> str:
"""Generate OAuth logout URL."""Pre-configured OAuth clients for popular authentication providers.
class GoogleAppClient:
"""
Google OAuth integration.
Provides authentication using Google OAuth 2.0 service.
"""
def __init__(self, client_id: str, client_secret: str, code: str, scope: list, project_id: str = None, **kwargs):
"""
Initialize Google OAuth client.
Args:
client_id: Google OAuth client ID
client_secret: Google OAuth client secret
code: Authorization code from OAuth flow
scope: OAuth scopes to request
project_id: Google Cloud project ID
**kwargs: Additional OAuth parameters
"""
@classmethod
def from_file(cls, fname: str, code: str, scope: list, **kwargs):
"""Create client from credentials file."""
def consent_url(self, proj: str) -> str:
"""Get consent screen URL."""
def creds(self):
"""Create Google credentials object."""
class GitHubAppClient:
"""
GitHub OAuth integration.
Provides authentication using GitHub OAuth service.
"""
def __init__(self, client_id: str, client_secret: str, code: str, scope: list, **kwargs):
"""
Initialize GitHub OAuth client.
Args:
client_id: GitHub OAuth client ID
client_secret: GitHub OAuth client secret
code: Authorization code from OAuth flow
scope: OAuth scopes to request
**kwargs: Additional OAuth parameters
"""
class HuggingFaceClient:
"""
Hugging Face OAuth integration.
Provides authentication using Hugging Face OAuth service.
"""
def __init__(self, client_id: str, client_secret: str, code: str, scope: list, state: str, **kwargs):
"""
Initialize Hugging Face OAuth client.
Args:
client_id: Hugging Face OAuth client ID
client_secret: Hugging Face OAuth client secret
code: Authorization code from OAuth flow
scope: OAuth scopes to request
state: State parameter for CSRF protection
**kwargs: Additional OAuth parameters
"""
class DiscordAppClient:
"""
Discord OAuth integration.
Provides authentication using Discord OAuth service.
"""
def __init__(self, client_id: str, client_secret: str, is_user: bool = False, perms: int = 0, scope: list = None, **kwargs):
"""
Initialize Discord OAuth client.
Args:
client_id: Discord OAuth client ID
client_secret: Discord OAuth client secret
is_user: Whether to use user authentication
perms: Permission bitfield
scope: OAuth scopes to request
**kwargs: Additional OAuth parameters
"""
def login_link(self, redirect_uri: str, scope: list = None, state: str = None) -> str:
"""Get Discord login URL."""
def parse_response(self, code: str, redirect_uri: str):
"""Parse OAuth callback response."""
class Auth0AppClient:
"""
Auth0 OAuth integration.
Provides authentication using Auth0 service.
"""
def __init__(self, domain: str, client_id: str, client_secret: str, code: str, scope: list, redirect_uri: str = "", **kwargs):
"""
Initialize Auth0 OAuth client.
Args:
domain: Auth0 domain
client_id: Auth0 client ID
client_secret: Auth0 client secret
code: Authorization code from OAuth flow
scope: OAuth scopes to request
redirect_uri: OAuth redirect URI
**kwargs: Additional OAuth parameters
"""
def login_link(self, req) -> str:
"""Get Auth0 login link for request."""HTTP Basic authentication middleware for simple username/password authentication.
class BasicAuthMiddleware:
"""
HTTP Basic authentication middleware.
Provides simple username/password authentication using
HTTP Basic authentication standard.
"""
def __init__(self, app, verifier, realm: str = 'Secure Area'):
"""
Initialize basic auth middleware.
Args:
app: ASGI application
verifier: Function to verify username/password
realm: Authentication realm name
"""
def user_pwd_auth(user: str, pwd: str) -> bool:
"""
Username/password authentication function.
Verify user credentials against stored values.
Args:
user: Username to verify
pwd: Password to verify
Returns:
bool: True if credentials are valid
"""
def basic_logout() -> str:
"""
Generate logout URL for basic authentication.
Returns:
str: Logout URL that clears basic auth credentials
"""Helper functions for OAuth flow management and user information handling.
def login_link(client, redirect_uri: str, **scope) -> str:
"""
Generate OAuth login URL.
Args:
client: OAuth client instance
redirect_uri: Redirect URI after authentication
**scope: OAuth scope parameters
Returns:
str: OAuth authorization URL
"""
def get_host(request) -> str:
"""
Extract host from request.
Args:
request: HTTP request object
Returns:
str: Host name from request headers
"""
def redir_url(request, redirect_uri: str) -> str:
"""
Build redirect URL from request.
Args:
request: HTTP request object
redirect_uri: Base redirect URI
Returns:
str: Complete redirect URL
"""
def parse_response(code: str, state: str, client) -> dict:
"""
Parse OAuth callback response.
Args:
code: Authorization code from OAuth provider
state: State parameter for CSRF protection
client: OAuth client instance
Returns:
dict: Parsed response with tokens and user info
"""
def get_info(client, token: str) -> dict:
"""
Get user info from OAuth provider.
Args:
client: OAuth client instance
token: Access token
Returns:
dict: User information from provider
"""
def retr_info(code: str, client) -> dict:
"""
Retrieve and parse user info from OAuth response.
Args:
code: Authorization code
client: OAuth client instance
Returns:
dict: Complete user information
"""
def retr_id(code: str, client) -> str:
"""
Retrieve user ID from OAuth response.
Args:
code: Authorization code
client: OAuth client instance
Returns:
str: User ID from OAuth provider
"""
def url_match(url: str, pattern: str) -> bool:
"""
Match URL patterns for OAuth callback handling.
Args:
url: URL to match
pattern: Pattern to match against
Returns:
bool: True if URL matches pattern
"""
def load_creds(filename: str) -> dict:
"""
Load saved OAuth credentials from file.
Args:
filename: File containing saved credentials
Returns:
dict: Loaded credentials
"""from fasthtml.common import *
import os
# Set up OAuth credentials (use environment variables in production)
GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID')
GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET')
app, rt = fast_app(secret_key='your-secret-key')
# Create Google OAuth client
google_client = GoogleAppClient(
client_id=GOOGLE_CLIENT_ID,
client_secret=GOOGLE_CLIENT_SECRET,
scope=['openid', 'email', 'profile']
)
# Set up OAuth handler
oauth = OAuth(
app=app,
client=google_client,
redirect_uri='/auth/callback',
logout_uri='/logout'
)
@rt('/')
def homepage(request):
user = request.session.get('user')
if user:
return Titled("Welcome",
Div(
H1(f"Welcome, {user.get('name', 'User')}!"),
P(f"Email: {user.get('email', 'Not provided')}"),
Img(src=user.get('picture', ''), alt="Profile picture", width="100"),
A("Logout", href="/logout", cls="button")
)
)
else:
return Titled("Login Required",
Div(
H1("Please log in"),
A("Login with Google", href="/auth/login", cls="button")
)
)
@rt('/auth/login')
def login(request):
# Generate login URL and redirect
login_url = oauth.login_link()
return Redirect(login_url)
@rt('/auth/callback')
def auth_callback(request):
# Handle OAuth callback
code = request.query_params.get('code')
if code:
try:
# Get user information
user_info = retr_info(code, google_client)
# Store user in session
request.session['user'] = user_info
return Redirect('/')
except Exception as e:
return Div(f"Authentication failed: {str(e)}")
else:
return Div("Authentication was cancelled")
@rt('/logout')
def logout(request):
# Clear session
request.session.clear()
return Redirect('/')
serve()from fasthtml.common import *
import os
app, rt = fast_app(secret_key='your-secret-key')
# GitHub OAuth client with repository access
github_client = GitHubAppClient(
client_id=os.getenv('GITHUB_CLIENT_ID'),
client_secret=os.getenv('GITHUB_CLIENT_SECRET'),
scope=['user:email', 'repo']
)
oauth = OAuth(app, github_client, '/auth/github/callback')
@rt('/')
def homepage(request):
user = request.session.get('user')
if user:
return Titled("GitHub Integration",
Div(
H1(f"Hello, {user.get('login', 'User')}!"),
P(f"GitHub Profile: {user.get('html_url', '')}"),
P(f"Repositories: {user.get('public_repos', 0)}"),
P(f"Followers: {user.get('followers', 0)}"),
A("View Repositories", href="/repos"),
Br(),
A("Logout", href="/logout")
)
)
else:
return Titled("GitHub Login",
A("Login with GitHub", href="/auth/github")
)
@rt('/auth/github')
def github_login():
return Redirect(oauth.login_link())
@rt('/auth/github/callback')
def github_callback(request):
code = request.query_params.get('code')
if code:
user_info = retr_info(code, github_client)
request.session['user'] = user_info
return Redirect('/')
return Redirect('/auth/github')
@rt('/repos')
def view_repos(request):
user = request.session.get('user')
if not user:
return Redirect('/')
# This would typically fetch repositories using the OAuth token
return Titled("Your Repositories",
Div(
H2("Your GitHub Repositories"),
P("Repository list would be displayed here using the GitHub API"),
A("Back to Home", href="/")
)
)from fasthtml.common import *
# Simple user database (use proper database in production)
USERS = {
'admin': 'secret123',
'user': 'password456'
}
def verify_credentials(username: str, password: str) -> bool:
"""Verify username and password"""
return USERS.get(username) == password
app, rt = fast_app()
# Add basic auth middleware
basic_auth = BasicAuthMiddleware(
app=app,
verifier=verify_credentials,
realm='Admin Area'
)
@rt('/')
def homepage(request):
# This route will require basic authentication
user = getattr(request, 'auth_user', 'Unknown')
return Titled("Protected Area",
Div(
H1(f"Welcome, {user}!"),
P("This is a protected area requiring authentication."),
A("Logout", href=basic_logout())
)
)
@rt('/login')
def login_form():
return Titled("Login",
Form(
Div(
Label("Username:", for_="username"),
Input(type="text", name="username", required=True),
cls="form-group"
),
Div(
Label("Password:", for_="password"),
Input(type="password", name="password", required=True),
cls="form-group"
),
Button("Login", type="submit"),
method="post",
action="/authenticate"
)
)
serve()from fasthtml.common import *
import os
app, rt = fast_app(secret_key='your-secret-key')
# Set up multiple OAuth providers
providers = {
'google': GoogleAppClient(
client_id=os.getenv('GOOGLE_CLIENT_ID'),
client_secret=os.getenv('GOOGLE_CLIENT_SECRET')
),
'github': GitHubAppClient(
client_id=os.getenv('GITHUB_CLIENT_ID'),
client_secret=os.getenv('GITHUB_CLIENT_SECRET')
),
'discord': DiscordAppClient(
client_id=os.getenv('DISCORD_CLIENT_ID'),
client_secret=os.getenv('DISCORD_CLIENT_SECRET')
)
}
@rt('/')
def homepage(request):
user = request.session.get('user')
provider = request.session.get('provider')
if user:
return Titled("Multi-Provider Login",
Div(
H1(f"Welcome from {provider.title()}!"),
P(f"Name: {user.get('name', user.get('login', 'User'))}"),
P(f"Email: {user.get('email', 'Not provided')}"),
A("Logout", href="/logout")
)
)
else:
return Titled("Choose Login Provider",
Div(
H1("Login Options"),
Div(
A("Login with Google", href="/auth/google", cls="btn btn-google"),
Br(), Br(),
A("Login with GitHub", href="/auth/github", cls="btn btn-github"),
Br(), Br(),
A("Login with Discord", href="/auth/discord", cls="btn btn-discord"),
cls="login-buttons"
)
)
)
@rt('/auth/{provider}')
def provider_login(provider: str):
if provider not in providers:
return Redirect('/')
client = providers[provider]
oauth = OAuth(app, client, f'/auth/{provider}/callback')
return Redirect(oauth.login_link())
@rt('/auth/{provider}/callback')
def provider_callback(provider: str, request):
if provider not in providers:
return Redirect('/')
code = request.query_params.get('code')
if code:
client = providers[provider]
user_info = retr_info(code, client)
request.session['user'] = user_info
request.session['provider'] = provider
return Redirect('/')
return Redirect('/')
@rt('/logout')
def logout(request):
request.session.clear()
return Redirect('/')from fasthtml.common import *
import hashlib
import secrets
app, rt = fast_app(db='auth.db', secret_key='your-secret-key')
# Create users table
users = app.db.users
if not users.exists():
users.create({
'id': int,
'username': str,
'email': str,
'password_hash': str,
'salt': str,
'created_at': str
}, pk='id')
def hash_password(password: str, salt: str = None) -> tuple[str, str]:
"""Hash password with salt"""
if salt is None:
salt = secrets.token_hex(16)
password_hash = hashlib.pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
salt.encode('utf-8'),
100000
).hex()
return password_hash, salt
def verify_password(password: str, password_hash: str, salt: str) -> bool:
"""Verify password against hash"""
computed_hash, _ = hash_password(password, salt)
return computed_hash == password_hash
@rt('/')
def homepage(request):
user_id = request.session.get('user_id')
if user_id:
user = users[user_id]
return Titled("Welcome",
Div(
H1(f"Welcome, {user.username}!"),
P(f"Email: {user.email}"),
A("Logout", href="/logout")
)
)
else:
return Titled("Authentication Demo",
Div(
H1("Please log in or register"),
A("Login", href="/login", cls="button"),
" ",
A("Register", href="/register", cls="button")
)
)
@rt('/register')
def register_form():
return Titled("Register",
Form(
Div(
Label("Username:", for_="username"),
Input(type="text", name="username", required=True),
cls="form-group"
),
Div(
Label("Email:", for_="email"),
Input(type="email", name="email", required=True),
cls="form-group"
),
Div(
Label("Password:", for_="password"),
Input(type="password", name="password", required=True),
cls="form-group"
),
Button("Register", type="submit"),
method="post",
action="/register/submit"
)
)
@rt('/register/submit', methods=['POST'])
def register_user(username: str, email: str, password: str):
# Check if user exists
existing = users.select().where('username = ? OR email = ?', username, email).first()
if existing:
return Div("Username or email already exists", style="color: red;")
# Hash password and create user
password_hash, salt = hash_password(password)
user_id = users.insert({
'username': username,
'email': email,
'password_hash': password_hash,
'salt': salt,
'created_at': str(datetime.now())
}).last_pk
# Log user in
request.session['user_id'] = user_id
return Redirect('/')
@rt('/login')
def login_form():
return Titled("Login",
Form(
Div(
Label("Username:", for_="username"),
Input(type="text", name="username", required=True),
cls="form-group"
),
Div(
Label("Password:", for_="password"),
Input(type="password", name="password", required=True),
cls="form-group"
),
Button("Login", type="submit"),
method="post",
action="/login/submit"
)
)
@rt('/login/submit', methods=['POST'])
def authenticate_user(username: str, password: str, request):
user = users.select().where('username = ?', username).first()
if user and verify_password(password, user.password_hash, user.salt):
request.session['user_id'] = user.id
return Redirect('/')
else:
return Div("Invalid username or password", style="color: red;")
@rt('/logout')
def logout(request):
request.session.clear()
return Redirect('/')Install with Tessl CLI
npx tessl i tessl/pypi-python-fasthtml