SQLAlchemy adapter¶
The default adapter. Uses SQLAlchemy 2.0 async. Ships with the standard extra (aiosqlite); use postgresql for asyncpg, or supply any async driver for MySQL.
Setup¶
from fastauth.adapters.sqlalchemy import SQLAlchemyAdapter
# engine_url form:
adapter = SQLAlchemyAdapter(engine_url="sqlite+aiosqlite:///./auth.db")
adapter = SQLAlchemyAdapter(engine_url="postgresql+asyncpg://user:pass@host/db")
# or pre-built engine:
adapter = SQLAlchemyAdapter(engine=my_async_engine)
Provide either engine_url or engine (raises ValueError otherwise). The shared async_sessionmaker is built with expire_on_commit=False.
Sub-adapters¶
SQLAlchemyAdapter exposes one property per sub-adapter, each lazily constructing a sub-adapter that shares the session factory:
| Property | Class | Used for |
|---|---|---|
.user |
SQLAlchemyUserAdapter |
config.adapter |
.token |
SQLAlchemyTokenAdapter |
config.token_adapter |
.session |
SQLAlchemySessionAdapter |
auth.session_adapter |
.role |
SQLAlchemyRoleAdapter |
auth.role_adapter |
.oauth |
SQLAlchemyOAuthAccountAdapter |
config.oauth_adapter |
.passkey |
SQLAlchemyPasskeyAdapter |
config.passkey_adapter |
config = FastAuthConfig(
...,
adapter=adapter.user,
token_adapter=adapter.token,
oauth_adapter=adapter.oauth, # only if you use OAuth
passkey_adapter=adapter.passkey, # only if you use passkeys
)
auth = FastAuth(config)
auth.role_adapter = adapter.role # only if you use RBAC
auth.session_adapter = adapter.session # only if you use /auth/sessions
Tables¶
SQLAlchemyAdapter manages seven tables under Base (a DeclarativeBase):
| Table | Purpose |
|---|---|
fastauth_users |
Users (id, email unique, hashed_password, email_verified, is_active, timestamps). |
fastauth_roles |
RBAC role names. |
fastauth_user_roles |
User ↔ role association. |
fastauth_role_permissions |
Role ↔ permission strings. |
fastauth_sessions |
Server-side session rows for /auth/sessions. |
fastauth_tokens |
One-time tokens (verification, reset, email change, magic-link request) + refresh JTIs + login attempts. |
fastauth_oauth_accounts |
Linked OAuth accounts; unique on (provider, provider_account_id). |
fastauth_passkeys |
Passkey credentials (public_key, sign_count, aaguid, name, last_used_at). |
Create / drop tables¶
@asynccontextmanager
async def lifespan(app: FastAPI):
await adapter.create_tables() # CREATE IF NOT EXISTS — safe to call repeatedly
yield
drop_tables() is provided for tests only:
@pytest.fixture
async def fresh_db():
adapter = SQLAlchemyAdapter(engine_url="sqlite+aiosqlite:///:memory:")
await adapter.create_tables()
yield adapter
await adapter.drop_tables()
Database URLs¶
| DB | URL prefix | Extra |
|---|---|---|
| SQLite | sqlite+aiosqlite:///./auth.db |
standard |
| PostgreSQL | postgresql+asyncpg://... |
postgresql (asyncpg) |
| MySQL | mysql+asyncmy://... |
install asyncmy yourself |
SQLite in production: not recommended. SQLite does not allow multiple concurrent writers; multi-worker FastAPI deployments will hit
database is locked. Use PostgreSQL in production.
Common mistakes¶
- Forgetting
await adapter.create_tables()— endpoints return500 no such table. - Switching DB engines in place — tables are not migrated automatically. Recreate or use Alembic.
- Sharing a single engine across processes — every process should construct its own
SQLAlchemyAdapter.