Skip to main content

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