Skip to content

Custom Adapter

You can use any database or ORM by implementing the UserAdapter protocol. FastAuth uses structural subtyping (duck typing) — your class just needs the right methods.

Implementing UserAdapter

from fastauth.types import UserData

class MyUserAdapter:
    """Example adapter backed by a hypothetical async ORM."""

    async def create_user(
        self, email: str, hashed_password: str | None = None, **kwargs
    ) -> UserData:
        record = await db.users.insert(email=email, hashed_password=hashed_password)
        return {"id": str(record.id), "email": record.email, "is_active": True}

    async def get_user_by_id(self, user_id: str) -> UserData | None:
        record = await db.users.find_one(id=user_id)
        return _to_user_data(record) if record else None

    async def get_user_by_email(self, email: str) -> UserData | None:
        record = await db.users.find_one(email=email)
        return _to_user_data(record) if record else None

    async def update_user(self, user_id: str, **kwargs) -> UserData:
        record = await db.users.update(id=user_id, **kwargs)
        return _to_user_data(record)

    async def delete_user(self, user_id: str, soft: bool = True) -> None:
        if soft:
            await db.users.update(id=user_id, is_active=False)
        else:
            await db.users.delete(id=user_id)

    async def get_hashed_password(self, user_id: str) -> str | None:
        record = await db.users.find_one(id=user_id)
        return record.hashed_password if record else None

    async def set_hashed_password(self, user_id: str, hashed_password: str) -> None:
        await db.users.update(id=user_id, hashed_password=hashed_password)

UserData shape

FastAuth expects a UserData TypedDict from every adapter method. At minimum:

{
    "id": "unique-user-id",       # str, required
    "email": "alice@example.com", # str, required
    "is_active": True,            # bool, required
    # Any extra fields are passed through to hooks and JWT claims
}

The full UserAdapter protocol

class UserAdapter(Protocol):
    async def create_user(self, email, hashed_password=None, **kwargs) -> UserData: ...
    async def get_user_by_id(self, user_id) -> UserData | None: ...
    async def get_user_by_email(self, email) -> UserData | None: ...
    async def update_user(self, user_id, **kwargs) -> UserData: ...
    async def delete_user(self, user_id, soft=True) -> None: ...
    async def get_hashed_password(self, user_id) -> str | None: ...
    async def set_hashed_password(self, user_id, hashed_password) -> None: ...

Other protocols

If you need token storage, OAuth account linking, or RBAC, implement the corresponding protocols:

Protocol Required for
TokenAdapter Email verification, password reset
OAuthAccountAdapter OAuth providers
SessionAdapter session_strategy="database"
RoleAdapter RBAC

All protocols are defined in fastauth.core.protocols. See the Protocols Reference for the full signatures.