Auth Adapters
Auth adapters provide a provider-agnostic interface for token verification on the backend. Each adapter implements the same AuthAdapter protocol, allowing you to switch between auth providers without changing your application code.
Adapter Interface
All auth adapters implement the AuthAdapter protocol:
from typing import Protocol
from uuid import UUID
from .base import Principal
class AuthAdapter(Protocol):
async def verify_token(self, token: str) -> Principal:
"""Verify a token and return the authenticated principal."""
...
async def issue_token(
self,
user_id: UUID | None = None,
claims: dict | None = None
) -> str:
"""Issue a new token (optional - some providers handle this client-side)."""
...
Available Adapters
NoAuthAdapter (Development)
For local development without authentication:
from boards.auth.adapters.none import NoAuthAdapter
# Any token will be accepted
adapter = NoAuthAdapter(
default_user_id="dev-user",
default_tenant="default"
)
Configuration:
BOARDS_AUTH_PROVIDER=none
BOARDS_AUTH_CONFIG='{"default_user_id": "my-dev-user"}'
JWTAuthAdapter
For self-managed JWT tokens:
from boards.auth.adapters.jwt import JWTAuthAdapter
adapter = JWTAuthAdapter(
secret="your-secret-key",
algorithm="HS256"
)
Configuration:
BOARDS_AUTH_PROVIDER=jwt
BOARDS_JWT_SECRET=your-secret-key
BOARDS_JWT_ALGORITHM=HS256 # Optional, defaults to HS256
SupabaseAuthAdapter
For Supabase authentication:
from boards.auth.adapters.supabase import SupabaseAuthAdapter
adapter = SupabaseAuthAdapter(
url="https://your-project.supabase.co",
service_role_key="your-service-role-key"
)
Configuration:
BOARDS_AUTH_PROVIDER=supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
ClerkAuthAdapter
For Clerk authentication:
from boards.auth.adapters.clerk import ClerkAuthAdapter
adapter = ClerkAuthAdapter(
secret_key="your-clerk-secret-key"
)
Configuration:
BOARDS_AUTH_PROVIDER=clerk
CLERK_SECRET_KEY=your-clerk-secret-key
Auth0AuthAdapter
For Auth0 authentication:
from boards.auth.adapters.auth0 import Auth0AuthAdapter
adapter = Auth0AuthAdapter(
domain="your-tenant.auth0.com",
audience="your-api-audience"
)
Configuration:
BOARDS_AUTH_PROVIDER=auth0
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_AUDIENCE=your-api-audience
Principal Structure
All adapters return a Principal object with consistent structure:
from typing import TypedDict, NotRequired, Literal
class Principal(TypedDict):
provider: Literal['supabase', 'clerk', 'auth0', 'oidc', 'jwt', 'none']
subject: str # Unique user ID from auth provider
email: NotRequired[str]
display_name: NotRequired[str]
avatar_url: NotRequired[str]
claims: NotRequired[dict] # Provider-specific claims
Using Adapters
Adapters are automatically configured based on environment variables:
from boards.auth.factory import get_auth_adapter
# Gets the configured adapter
adapter = get_auth_adapter()
# Use in middleware
principal = await adapter.verify_token(token)
Custom Adapters
You can create custom adapters by implementing the AuthAdapter protocol:
from boards.auth.adapters.base import AuthAdapter, Principal, AuthenticationError
class CustomAuthAdapter:
async def verify_token(self, token: str) -> Principal:
# Your verification logic here
if not self.is_valid(token):
raise AuthenticationError("Invalid token")
return Principal(
provider="custom",
subject="user-123",
email="user@example.com"
)
async def issue_token(self, user_id=None, claims=None) -> str:
# Your token issuance logic
return "custom-token"
Then register it in your app:
from boards.auth.factory import register_auth_adapter
from .my_adapter import CustomAuthAdapter
# Register your adapter
register_auth_adapter("custom", lambda config: CustomAuthAdapter(**config))
# Configure via environment
BOARDS_AUTH_PROVIDER=custom
BOARDS_AUTH_CONFIG='{"custom_option": "value"}'
Error Handling
All adapters should raise AuthenticationError for auth failures:
from boards.auth.adapters.base import AuthenticationError
try:
principal = await adapter.verify_token(token)
except AuthenticationError as e:
# Handle authentication failure
return {"error": "Unauthorized", "message": str(e)}
Testing
Test your adapters with the provided test utilities:
import pytest
from boards.auth.adapters.jwt import JWTAuthAdapter
from boards.auth.adapters.base import AuthenticationError
@pytest.mark.asyncio
async def test_jwt_adapter():
adapter = JWTAuthAdapter(secret="test-secret")
# Test valid token
token = await adapter.issue_token(claims={"sub": "user-123"})
principal = await adapter.verify_token(token)
assert principal["provider"] == "jwt"
assert principal["subject"] == "user-123"
# Test invalid token
with pytest.raises(AuthenticationError):
await adapter.verify_token("invalid-token")