Skip to main content

Flexible authentication system for FastAPI applications

Project description

FastSecure

FastSecure logo

A modern, flexible authentication system for FastAPI applications with support for multiple authentication methods and strategies.

Table of Contents

[!WARNING]
FastSecure is still experimental.

Features

  • 🔐 Multiple Authentication Methods

    • JWT tokens with refresh token support and flexible configuration
    • Session-based authentication with configurable storage backends
    • OAuth 2.0 providers (Google, GitHub) with standardized user info
    • Easy to extend with custom authentication providers
  • 🔄 Flexible Authentication Strategies

    • Use single or multiple authentication methods
    • Support for AND logic (require all methods)
    • Support for OR logic (allow any method)
    • Optional authentication methods
    • Path-based authentication requirements
  • 🗄️ Session Storage Options

    • In-memory storage for development
    • Redis backend for distributed systems
    • Database backend (SQLAlchemy) for persistence
    • Easy to implement custom storage backends
  • 🛡️ Security Features

    • Token expiration and refresh
    • Session timeout and cleanup
    • Concurrent session limits
    • Scope-based authorization
    • IP tracking and user agent logging

Installation

  1. Install using pip:
pip install fastsecure
  1. Install using uv or poetry:
uv add fastsecure
poetry add fastsecure

Core Concepts

Before diving into the implementation, let's understand the core concepts:

Authentication Manager

The AuthenticationManager is the central component that:

  • Manages authentication providers
  • Handles authentication strategies
  • Enforces authentication requirements
  • Coordinates the authentication flow

Authentication Providers

Providers implement specific authentication methods:

  • JWT tokens
  • Sessions
  • OAuth
  • Custom methods

Authentication Strategies

Strategies determine how multiple authentication methods are combined:

  • AuthStrategy.ANY: Any provider can authenticate (OR logic)
  • AuthStrategy.ALL: All providers must authenticate (AND logic)

Authentication Requirements

Requirements specify which authentication methods are needed for specific paths:

  • Required providers
  • Optional providers
  • Authentication strategy
  • Required scopes

Basic Usage Guide

Setting Up JWT Authentication

  1. Create a Basic FastAPI Application
from fastapi import FastAPI, Depends, HTTPException
from fastsecure import AuthenticationManager, JWTAuthenticationProvider

app = FastAPI()
  1. Initialize Authentication Manager and JWT Provider
# Initialize authentication
auth_manager = AuthenticationManager()

# Configure JWT provider
jwt_auth = JWTAuthenticationProvider(
    secret_key="your-secret-key",  # Use a secure key in production!
    access_token_expire_minutes=30,
    refresh_token_expire_days=7
)

# Register the provider
auth_manager.register_provider("jwt", jwt_auth)
  1. Create Authentication Dependency
from fastapi import Request
from typing import Optional

async def requires_auth(request: Request, path: Optional[str] = None) -> dict:
    """
    FastAPI dependency for authentication requirements.
    
    Args:
        request: The FastAPI request object
        path: Optional path override for authentication requirements
    
    Returns:
        Authentication result if successful
    
    Raises:
        HTTPException: If authentication fails
    """
    # Get authentication data from request headers
    auth_header = request.headers.get("Authorization")
    if not auth_header:
        raise HTTPException(status_code=401, detail="No authentication provided")
    
    # Parse Bearer token
    try:
        scheme, token = auth_header.split()
        if scheme.lower() != "bearer":
            raise HTTPException(status_code=401, detail="Invalid authentication scheme")
    except ValueError:
        raise HTTPException(status_code=401, detail="Invalid authorization header")
    
    # Authenticate using the manager
    result = await auth_manager.authenticate(
        path or request.url.path,
        {"jwt": {"access_token": token}}
    )
    
    if not result.success:
        raise HTTPException(
            status_code=401,
            detail="Authentication failed",
            headers={"WWW-Authenticate": "Bearer"}
        )
    
    return result
  1. Configure Protected Routes
# Add authentication requirement for protected paths
auth_manager.add_requirement(
    "/api/protected/*",  # Path pattern
    providers=["jwt"],   # Required providers
    scopes=["read"]      # Required scopes (optional)
)
  1. Implement Login and Protected Routes
from pydantic import BaseModel

class LoginCredentials(BaseModel):
    username: str
    password: str

@app.post("/api/auth/login")
async def login(credentials: LoginCredentials):
    # Validate credentials (implement your own validation)
    user_id = validate_credentials(credentials)
    
    # Authenticate with JWT provider
    result = await auth_manager.authenticate(
        "/api/protected/data",
        {"jwt": {
            "user_id": user_id,
            "scopes": ["read", "write"]
        }}
    )
    
    if not result.success:
        raise HTTPException(401, "Authentication failed")
    
    return {
        "access_token": result.access_token,
        "refresh_token": result.refresh_token,
        "token_type": "Bearer",
        "expires_at": result.expires_at
    }

@app.get("/api/protected/data")
async def protected_data(auth = Depends(requires_auth)):
    return {
        "message": "Authenticated!",
        "user_id": auth.user_id,
        "scopes": auth.metadata.get("scopes", [])
    }

Setting Up Session Authentication

  1. Choose a Storage Backend
from fastsecure import (
    SessionAuthenticationProvider,
    RedisSessionStore,
    DatabaseSessionStore
)

# For development (in-memory)
session_auth = SessionAuthenticationProvider()

# For production with Redis
session_auth = SessionAuthenticationProvider(
    session_store=RedisSessionStore("redis://localhost"),
    session_timeout_minutes=60,
    max_sessions_per_user=3,
    cleanup_expired=True
)
  1. Configure Session Authentication
# Register the provider
auth_manager.register_provider("session", session_auth)

# Add requirements
auth_manager.add_requirement(
    "/api/user/*",
    providers=["session"]
)
  1. Implement Session Login
@app.post("/api/auth/session/login")
async def session_login(
    credentials: LoginCredentials,
    request: Request
):
    user_id = validate_credentials(credentials)
    
    result = await auth_manager.authenticate(
        "/api/user/profile",
        {"session": {
            "user_id": user_id,
            "ip_address": request.client.host,
            "user_agent": request.headers.get("user-agent"),
            "scopes": ["user:read"]
        }}
    )
    
    if not result.success:
        raise HTTPException(401, "Authentication failed")
    
    response = JSONResponse({
        "message": "Logged in successfully",
        "user_id": user_id
    })
    
    # Set session cookie
    response.set_cookie(
        "session_id",
        result.session_id,
        httponly=True,
        secure=True,
        samesite="lax",
        expires=result.expires_at
    )
    
    return response

Setting Up OAuth Authentication

  1. Configure OAuth Providers
from fastsecure import GoogleAuthProvider, GitHubAuthProvider

# Google Sign-In
google_auth = GoogleAuthProvider(
    client_id="your-client-id",
    client_secret="your-client-secret",
    redirect_uri="http://localhost:8000/auth/google/callback"
)

# GitHub Sign-In
github_auth = GitHubAuthProvider(
    client_id="your-client-id",
    client_secret="your-client-secret",
    redirect_uri="http://localhost:8000/auth/github/callback"
)

# Register providers
auth_manager.register_provider("google", google_auth)
auth_manager.register_provider("github", github_auth)
  1. Implement OAuth Flow
@app.get("/auth/google/login")
async def google_login():
    authorization_url = google_auth.get_authorization_url(
        state="random-secure-state"  # Implement secure state handling
    )
    return RedirectResponse(authorization_url)

@app.get("/auth/google/callback")
async def google_callback(code: str, state: str):
    # Validate state
    validate_oauth_state(state)
    
    result = await auth_manager.authenticate(
        "/api/user/profile",
        {"google": {"code": code}}
    )
    
    if not result.success:
        raise HTTPException(401, "Authentication failed")
    
    # Get user info from result
    user_info = result.metadata["user_info"]
    
    return {
        "message": "Logged in with Google",
        "email": user_info["email"],
        "name": user_info["name"],
        "access_token": result.access_token
    }

Advanced Usage Guide

Combining Authentication Methods

  1. Require Multiple Methods (AND Strategy)
# Require both JWT and session authentication
auth_manager.add_requirement(
    "/api/admin/*",
    providers=["jwt", "session"],
    strategy=AuthStrategy.ALL,
    scopes=["admin"]
)

# Example request handler
@app.post("/api/admin/action")
async def admin_action(auth = Depends(auth_manager.requires_auth)):
    if "admin" not in auth.metadata.get("scopes", []):
        raise HTTPException(403, "Insufficient permissions")
    return {"message": "Admin action successful"}
  1. Allow Alternative Methods (OR Strategy)
# Allow any authentication method
auth_manager.add_requirement(
    "/api/user/*",
    providers=["jwt", "session", "google"],
    strategy=AuthStrategy.ANY
)
  1. Optional Authentication
# Add optional authentication methods
auth_manager.add_requirement(
    "/api/public/*",
    providers=["jwt"],
    optional_providers=["session", "google"]
)

Custom Authentication Providers

  1. Create a Custom Provider
from fastsecure import AuthenticationProvider, AuthenticationResult
from typing import Dict, Any, Set

class APIKeyAuthProvider(AuthenticationProvider):
    def __init__(self, api_keys: Dict[str, str]):
        self.api_keys = api_keys
    
    def get_required_credentials(self) -> Set[str]:
        return {"api_key"}
    
    async def authenticate(
        self,
        credentials: Dict[str, Any]
    ) -> AuthenticationResult:
        api_key = credentials.get("api_key")
        
        if api_key not in self.api_keys:
            return AuthenticationResult(
                success=False,
                provider=self.provider_name,
                metadata={"error": "Invalid API key"}
            )
        
        return AuthenticationResult(
            success=True,
            provider=self.provider_name,
            user_id=self.api_keys[api_key],
            metadata={"api_key": api_key}
        )
    
    async def validate_authentication(
        self,
        auth_data: Dict[str, Any]
    ) -> bool:
        return auth_data.get("api_key") in self.api_keys
  1. Register and Use Custom Provider
# Initialize with API keys
api_key_auth = APIKeyAuthProvider({
    "key1": "user1",
    "key2": "user2"
})

# Register provider
auth_manager.register_provider("apikey", api_key_auth)

# Add requirement
auth_manager.add_requirement(
    "/api/service/*",
    providers=["apikey"]
)

Storage Backends

  1. Implement Custom Session Storage
from fastsecure import SessionStore
from typing import Dict, Any, List, Optional
from datetime import datetime

class CustomSessionStore(SessionStore):
    async def create_session(
        self,
        user_id: int,
        session_id: str,
        expires_at: datetime,
        metadata: Dict[str, Any]
    ) -> bool:
        # Implement session creation
        pass
    
    async def get_session(
        self,
        session_id: str
    ) -> Optional[Dict[str, Any]]:
        # Implement session retrieval
        pass
    
    async def update_session(
        self,
        session_id: str,
        metadata: Dict[str, Any]
    ) -> bool:
        # Implement session update
        pass
    
    async def delete_session(
        self,
        session_id: str
    ) -> bool:
        # Implement session deletion
        pass
    
    async def get_user_sessions(
        self,
        user_id: int
    ) -> List[Dict[str, Any]]:
        # Implement user sessions retrieval
        pass

Security Best Practices

  1. JWT Security

    • Use strong secret keys
    • Set appropriate token expiration times
    • Implement token refresh securely
    • Use HTTPS for token transmission
  2. Session Security

    • Enable secure cookie attributes
    • Implement session timeout
    • Limit concurrent sessions
    • Clean up expired sessions
  3. OAuth Security

    • Validate OAuth state parameter
    • Use HTTPS for callbacks
    • Validate token scopes
    • Handle user data securely
  4. General Security

    • Implement rate limiting
    • Use secure password handling
    • Log security events
    • Regular security audits

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contact

Igor Benav – @igorbenavigormagalhaesr@gmail.com github.com/igorbenav

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastsecure-0.3.0.tar.gz (47.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastsecure-0.3.0-py3-none-any.whl (42.0 kB view details)

Uploaded Python 3

File details

Details for the file fastsecure-0.3.0.tar.gz.

File metadata

  • Download URL: fastsecure-0.3.0.tar.gz
  • Upload date:
  • Size: 47.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.4.18

File hashes

Hashes for fastsecure-0.3.0.tar.gz
Algorithm Hash digest
SHA256 bfaf9830965fce198c0ff2c4b0cfea2c5d153b79dd92fa64c3c0555a009b64e9
MD5 2321aa1506cd23901a41b6e693f2edf9
BLAKE2b-256 4912c9abbd8e08bbc26898e9db7084cc09619f289f3aefdb3216d6641d59baba

See more details on using hashes here.

File details

Details for the file fastsecure-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: fastsecure-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 42.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.4.18

File hashes

Hashes for fastsecure-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 33050a45af64771b49967a63de8d892e54c0c4f5e2ca391b0e7f98221dbbfa89
MD5 c091e3d02469ba5bab50952190a22ee1
BLAKE2b-256 ca0359dd272c39997f1fbfe11fdac7cb2185608aad2166312a86c3437e219d0b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page