Skip to main content

Authenticator

The Framefox authentication system is built around the AuthenticatorInterface and AbstractAuthenticator classes, which provide a flexible foundation for implementing various authentication strategies. Source: framefox/core/security/authenticator/

AuthenticatorInterface

The interface that all authenticators must implement.
class AuthenticatorInterface(ABC):
    @abstractmethod
    async def authenticate(self, request: Request) -> Passport | None:
        pass
    
    @abstractmethod
    async def on_authentication_success(
        self, request: Request, passport: Passport
    ) -> Response | None:
        pass
    
    @abstractmethod
    async def on_authentication_failure(
        self, request: Request, exception: Exception
    ) -> Response | None:
        pass

AbstractAuthenticator

Base class providing common authentication functionality. Source: framefox/core/security/authenticator/abstract_authenticator.py

Methods

authenticate()

Attempts to authenticate the user from the request.
async def authenticate(self, request: Request) -> Passport | None
request
Request
required
The incoming HTTP request
return
Passport | None
A Passport object containing user information if authentication succeeds, None otherwise
Returns:
  • Passport object with user data if credentials are valid
  • None if authentication fails or credentials are missing

on_authentication_success()

Called when authentication succeeds.
async def on_authentication_success(
    self, request: Request, passport: Passport
) -> Response | None
request
Request
required
The HTTP request
passport
Passport
required
The passport containing authenticated user information
return
Response | None
Optional response (e.g., redirect). Return None to continue with the original request.

on_authentication_failure()

Called when authentication fails.
async def on_authentication_failure(
    self, request: Request, exception: Exception
) -> Response | None
request
Request
required
The HTTP request
exception
Exception
required
The exception that caused authentication to fail
return
Response | None
Optional response (e.g., redirect to login page)

Creating a Custom Authenticator

Form-Based Authenticator

from framefox.core.security.authenticator.abstract_authenticator import AbstractAuthenticator
from framefox.core.security.passport.passport import Passport
from framefox.core.security.user.user_badge import UserBadge
from starlette.requests import Request
from starlette.responses import Response, RedirectResponse
import logging

class FormLoginAuthenticator(AbstractAuthenticator):
    def __init__(self, user_repository, password_hasher):
        self.user_repository = user_repository
        self.password_hasher = password_hasher
        self.logger = logging.getLogger("FormAuthenticator")
    
    async def authenticate(self, request: Request) -> Passport | None:
        # Only authenticate on login POST
        if request.url.path != "/login" or request.method != "POST":
            return None
        
        # Get credentials from form
        form = await request.form()
        email = form.get("email")
        password = form.get("password")
        
        if not email or not password:
            return None
        
        # Find user
        user = await self.user_repository.find_one_by({"email": email})
        if not user:
            self.logger.warning(f"User not found: {email}")
            return None
        
        # Verify password
        if not self.password_hasher.verify(password, user.password):
            self.logger.warning(f"Invalid password for user: {email}")
            return None
        
        # Create passport
        user_badge = UserBadge(user.id, user.email)
        return Passport(user_badge, user.roles)
    
    async def on_authentication_success(
        self, request: Request, passport: Passport
    ) -> Response | None:
        self.logger.info(f"User authenticated: {passport.user_badge.email}")
        return RedirectResponse(url="/dashboard", status_code=302)
    
    async def on_authentication_failure(
        self, request: Request, exception: Exception
    ) -> Response | None:
        self.logger.error(f"Authentication failed: {exception}")
        return RedirectResponse(url="/login?error=1", status_code=302)

JWT Authenticator

from framefox.core.security.authenticator.abstract_authenticator import AbstractAuthenticator
from framefox.core.security.passport.passport import Passport
from framefox.core.security.user.user_badge import UserBadge
from framefox.core.security.token_manager import TokenManager
from starlette.requests import Request
import logging

class JWTAuthenticator(AbstractAuthenticator):
    def __init__(self, token_manager: TokenManager):
        self.token_manager = token_manager
        self.logger = logging.getLogger("JWTAuthenticator")
    
    async def authenticate(self, request: Request) -> Passport | None:
        # Get 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[7:]  # Remove "Bearer " prefix
        
        # Decode token
        payload = self.token_manager.decode_token(token)
        if not payload:
            return None
        
        # Create passport from token data
        user_badge = UserBadge(payload["sub"], payload["email"])
        return Passport(user_badge, payload.get("roles", []))
    
    async def on_authentication_success(
        self, request: Request, passport: Passport
    ) -> Response | None:
        # No redirect needed for API authentication
        return None
    
    async def on_authentication_failure(
        self, request: Request, exception: Exception
    ) -> Response | None:
        # Return 401 for API
        from fastapi.responses import JSONResponse
        return JSONResponse(
            {"error": "Unauthorized"},
            status_code=401
        )

OAuth Authenticator

For OAuth providers (Google, GitHub, etc.), extend AbstractOAuthAuthenticator:
from framefox.core.security.authenticator.abstract_oauth_authenticator import AbstractOAuthAuthenticator

class GoogleAuthenticator(AbstractOAuthAuthenticator):
    def __init__(self, client_id: str, client_secret: str):
        super().__init__(
            provider_name="google",
            authorization_url="https://accounts.google.com/o/oauth2/v2/auth",
            token_url="https://oauth2.googleapis.com/token",
            user_info_url="https://www.googleapis.com/oauth2/v2/userinfo",
            client_id=client_id,
            client_secret=client_secret,
            scopes=["openid", "email", "profile"]
        )

Registration

Authenticators are configured in config/security.yaml:
security:
  firewalls:
    main:
      authenticators:
        - FormLoginAuthenticator
        - JWTAuthenticator
      entry_point: /login

See Also

Passport

User authentication flow

Token Manager

JWT token management

Access Manager

Role-based access control

Security Guide

Complete security documentation

Build docs developers (and LLMs) love