CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-python-fasthtml

The fastest way to create HTML apps - a next-generation Python web framework for building fast, scalable web applications with minimal code

Pending
Overview
Eval results
Files

authentication.mddocs/

Authentication and Security

OAuth integration with multiple providers, basic authentication middleware, and session management for secure web applications.

Capabilities

OAuth Integration

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."""

OAuth Provider Clients

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."""

Basic Authentication Middleware

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
    """

OAuth Utility Functions

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
    """

Usage Examples

Google OAuth Authentication

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()

GitHub OAuth with User Permissions

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="/")
        )
    )

Basic Authentication

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()

Multi-Provider OAuth

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('/')

Custom Authentication with Database

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

docs

application-routing.md

authentication.md

css-styling.md

development-tools.md

form-handling.md

html-components.md

htmx-integration.md

index.md

javascript-integration.md

notifications.md

svg-components.md

tile.json