Skip to main content
Framefox provides a flexible authentication system supporting multiple authentication methods including form-based authentication, JWT tokens, and OAuth. The system is built around the concept of authenticators, passports, and firewalls.

Authentication Architecture

The authentication flow involves several key components:
  1. Authenticator: Handles the authentication logic
  2. Passport: Contains user credentials and authentication state
  3. Firewall: Defines authentication rules for URL patterns
  4. TokenManager: Manages JWT token creation and validation
  5. UserProvider: Retrieves authenticated user information

Configuring Authentication

Basic Setup

Configure authentication in config/security.yaml:
security:
  firewalls:
    main:
      pattern: ^/
      authenticator: app.security.FormAuthenticator
      login_path: /login
      logout_path: /logout
      provider: app_user_provider
      default_target_path: /dashboard
  
  providers:
    app_user_provider:
      entity:
        class: app.entity.user.User
        property: email

Multiple Firewalls

You can configure multiple firewalls for different parts of your application:
security:
  firewalls:
    api:
      pattern: ^/api
      authenticator: app.security.JWTAuthenticator
      stateless: true
      provider: api_user_provider
    
    main:
      pattern: ^/
      authenticator: app.security.FormAuthenticator
      login_path: /login
      logout_path: /logout
      provider: app_user_provider

Creating an Authenticator

Form-Based Authenticator

Create a custom authenticator by extending AbstractAuthenticator:
from fastapi import Request
from framefox.core.security.authenticator.abstract_authenticator import AbstractAuthenticator
from framefox.core.security.passport.passport import Passport
from framefox.core.security.passport.user_badge import UserBadge
from framefox.core.security.passport.password_credentials import PasswordCredentials
from framefox.core.security.passport.csrf_token_badge import CsrfTokenBadge
from fastapi.responses import RedirectResponse

class FormAuthenticator(AbstractAuthenticator):
    async def authenticate(self, request: Request) -> Passport:
        """Extract credentials from form and create passport."""
        form_data = await request.form()
        
        username = form_data.get("_username")
        password = form_data.get("_password")
        csrf_token = form_data.get("csrf_token")
        
        if not username or not password:
            return None
        
        # Create user badge with identifier
        user_badge = UserBadge(
            user_identifier=username,
            user_identifier_property="email"
        )
        
        # Create password credentials
        password_credentials = PasswordCredentials(raw_password=password)
        
        # Create CSRF token badge
        csrf_token_badge = CsrfTokenBadge(csrf_token) if csrf_token else None
        
        # Create and return passport
        passport = Passport(
            user_badge=user_badge,
            password_credentials=password_credentials,
            csrf_token_badge=csrf_token_badge
        )
        
        return passport
    
    def on_auth_success(self, token: str) -> RedirectResponse:
        """Handle successful authentication."""
        response = RedirectResponse(url="/dashboard", status_code=303)
        response.set_cookie(
            key="access_token",
            value=token,
            httponly=True,
            secure=True,
            samesite="lax"
        )
        return response
    
    def on_auth_failure(self, request: Request, error: str = None) -> RedirectResponse:
        """Handle authentication failure."""
        return RedirectResponse(url="/login?error=1", status_code=303)
Reference: /home/daytona/workspace/source/framefox/core/security/authenticator/abstract_authenticator.py:20

Understanding Passports

The Passport class holds authentication information:
class Passport:
    def __init__(
        self,
        user_badge: UserBadge,
        password_credentials: Optional[PasswordCredentials] = None,
        csrf_token_badge: Optional[CsrfTokenBadge] = None,
        provider_info=None,
    ):
        self.user_badge = user_badge
        self.password_credentials = password_credentials
        self.csrf_token_badge = csrf_token_badge
        self.user = None
        self.roles = []
        self.provider_info = provider_info
Reference: /home/daytona/workspace/source/framefox/core/security/passport/passport.py:30

Authentication Flow

The passport authenticates the user in several steps:
  1. User Lookup: Finds user by identifier (email, username, etc.)
  2. Password Verification: Validates password using bcrypt
  3. Role Assignment: Assigns user roles from the database
async def authenticate_user(self) -> bool:
    if not self.user_badge:
        return False
    
    if self.provider_info:
        repository = self.provider_info.get("repository")
        property_name = self.provider_info.get("property")
        
        if repository and property_name:
            self.user_badge.user_identifier_property = property_name
            user = await self.user_badge.get_user(repository)
            
            if not user:
                return False
            
            self.user = user
    
    # Verify password if provided
    if self.password_credentials:
        password_hasher = PasswordHasher()
        authenticated = password_hasher.verify(
            self.password_credentials.raw_password,
            self.user.password
        )
        
        if not authenticated:
            return False
    
    self.roles = self.user.roles
    return True
Reference: /home/daytona/workspace/source/framefox/core/security/passport/passport.py:45

Login & Logout Flows

Login Flow

  1. User submits login form to login_path
  2. Firewall intercepts the request
  3. CSRF token is validated
  4. Authenticator extracts credentials and creates passport
  5. Passport authenticates user against database
  6. Brute force protection checks are applied
  7. JWT token is created and stored
  8. User is redirected to default_target_path
# Login form template
<form method="POST" action="/login">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    
    <label for="email">Email:</label>
    <input type="email" name="_username" id="email" required>
    
    <label for="password">Password:</label>
    <input type="password" name="_password" id="password" required>
    
    <button type="submit">Login</button>
</form>

Logout Flow

  1. User accesses logout_path
  2. Firewall handles the logout request
  3. Token is cleared from storage
  4. Session is deleted
  5. Cookies are removed
  6. User is redirected to login page
async def handle_logout(self, request: Request, firewall_config: Dict, 
                       firewall_name: str, call_next) -> Response:
    # Clear token storage
    self.token_storage.clear_token()
    
    # Delete session
    session_id = request.cookies.get("session_id")
    if session_id:
        self.session_manager.delete_session(session_id)
    
    # Clear session data
    self.session.clear()
    request.state.session_data = {}
    
    # Create response and delete cookies
    response = RedirectResponse(url="/login", status_code=302)
    self.cookie_manager.delete_cookie(response, "session_id")
    self.cookie_manager.delete_cookie(response, "csrf_token")
    self.cookie_manager.delete_cookie(response, "access_token")
    
    return response
Reference: /home/daytona/workspace/source/framefox/core/security/handlers/firewall_handler.py:382

Session Management

JWT Tokens

Framefox uses JWT tokens for session management:
class TokenManager:
    def create_token(self, user, firewallname: str, roles: list = None) -> str:
        payload = {
            "sub": str(user.id),
            "email": user.email,
            "roles": roles or [],
            "firewallname": firewallname,
            "iat": int(time.time()),
            "exp": int(time.time()) + self.expiration,  # 1 hour default
        }
        
        return jwt.encode(payload, self.secret_key, algorithm="HS256")
Reference: /home/daytona/workspace/source/framefox/core/security/token_manager.py:28

Token Validation

def decode_token(self, token: str) -> dict:
    try:
        payload = jwt.decode(
            token, 
            self.settings.session_secret_key, 
            algorithms=[self.algorithm]
        )
        return payload
    except jwt.ExpiredSignatureError:
        self.logger.warning("Token expired.")
        return None
    except jwt.InvalidTokenError:
        self.logger.warning("Invalid token.")
        return None
Reference: /home/daytona/workspace/source/framefox/core/security/token_manager.py:59

Token Storage

Tokens are stored in cookies and can be accessed via TokenStorage:
from framefox.core.security.token_storage import TokenStorage

# In your controller
def get_current_token(self, token_storage: TokenStorage):
    token = token_storage.get_token()
    payload = token_storage.get_payload()
    return payload

JWT Stateless Authentication

For API endpoints, use stateless JWT authentication:
class JWTAuthenticator(AbstractAuthenticator):
    async def authenticate(self, request: Request) -> Passport:
        # Extract token from Authorization header
        auth_header = request.headers.get("Authorization")
        if not auth_header or not auth_header.startswith("Bearer "):
            return None
        
        token = auth_header.replace("Bearer ", "")
        
        # Decode and validate token
        payload = self.token_manager.decode_token(token)
        if not payload:
            return None
        
        # Create user badge from payload
        user_badge = UserBadge(
            user_identifier=payload.get("email"),
            user_identifier_property="email"
        )
        
        # Create passport (no password validation for JWT)
        passport = Passport(user_badge=user_badge)
        
        return passport
    
    def on_auth_failure(self, request: Request, error: str = None):
        return JSONResponse(
            {"error": "Unauthorized", "message": error},
            status_code=401
        )

API Authentication Example

import httpx

# Login to get token
response = httpx.post("http://api.example.com/login", json={
    "email": "[email protected]",
    "password": "secret"
})
token = response.json()["token"]

# Use token for authenticated requests
headers = {"Authorization": f"Bearer {token}"}
response = httpx.get("http://api.example.com/api/users", headers=headers)

OAuth Authentication

Framefox supports OAuth authentication for third-party providers:
from framefox.core.security.authenticator.abstract_oauth_authenticator import AbstractOAuthAuthenticator

class GoogleOAuthAuthenticator(AbstractOAuthAuthenticator):
    def __init__(self):
        super().__init__()
        self.client_id = "your-client-id"
        self.client_secret = "your-client-secret"
        self.redirect_uri = "http://localhost:8000/auth/google/callback"
    
    async def get_oauth_user_data(self, code: str) -> dict:
        # Exchange code for access token
        # Fetch user data from Google
        # Return user data
        pass
Reference: /home/daytona/workspace/source/framefox/core/security/authenticator/abstract_oauth_authenticator.py:1

Security Considerations

1. Password Security

from framefox.core.security.password.password_hasher import PasswordHasher

hasher = PasswordHasher()  # Uses bcrypt by default
hashed = hasher.hash("user_password")

2. Token Security

  • Tokens are signed with SESSION_SECRET_KEY
  • Tokens expire after 1 hour (configurable)
  • Tokens are stored in HTTP-only cookies
  • Use HTTPS in production

3. Brute Force Protection

  • Progressive delays after failed attempts
  • IP blocking after 25 attempts/hour
  • Account locking after 15 attempts/hour
Reference: /home/daytona/workspace/source/framefox/core/security/protector/brute_force_protector.py:39

4. Timing Attack Protection

# Constant-time comparison for tokens
import secrets
if not secrets.compare_digest(token1, token2):
    raise InvalidTokenException()

Next Steps

User Management

Learn about user models and providers

CSRF Protection

Implement CSRF protection in your forms

Build docs developers (and LLMs) love