Storage backends
The library never imports an ORM. Storage is two async Protocols:
CredentialRepository (persistent passkeys) and ChallengeStore (in-flight challenges).
Shipped adapters
# Core (no extras)
from fastapi_passkeys.contrib import (
InMemoryCredentialRepository,
InMemoryChallengeStore,
StatelessChallengeStore,
)
SQLAlchemy ([sqlalchemy])
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from fastapi_passkeys.contrib.sqlalchemy import PasskeyBase, SqlAlchemyCredentialRepository
engine = create_async_engine("postgresql+asyncpg://localhost/app")
async with engine.begin() as conn:
await conn.run_sync(PasskeyBase.metadata.create_all)
repo = SqlAlchemyCredentialRepository(async_sessionmaker(engine, expire_on_commit=False))
user_id is a plain indexed string (the WebAuthn user handle). Add a foreign key to your
users table in your own migration if you want referential integrity.
Redis challenge store ([redis])
from redis.asyncio import Redis
from fastapi_passkeys.contrib.redis import RedisChallengeStore
store = RedisChallengeStore(Redis.from_url("redis://localhost:6379/0"))
Writing your own
Implement the Protocol against any store, then prove it with the shipped contract suite:
import pytest
from fastapi_passkeys.testing import check_credential_repository, check_challenge_store
@pytest.mark.asyncio
async def test_my_repo():
await check_credential_repository(MyCredentialRepository)
@pytest.mark.asyncio
async def test_my_store():
await check_challenge_store(lambda clock: MyChallengeStore(clock=clock))
This is exactly how the SQLAlchemy, Redis, and in-memory adapters are tested — so an SQLModel, Tortoise, or Beanie adapter you write is held to the same bar.