Skip to content

Recipes

End-to-end examples that combine several customization seams. Each one is self-contained - copy it and adjust. For the individual building blocks, see Customization.

Multi-tenant SaaS

Give every user a tenant_id, stamp it into the JWT, and read it on each request without a database lookup. This combines a custom field, custom claims, and a custom dependency.

1. Add the column to your model

from sqlmodel import Field, Relationship

from fastapi_fullauth.models.sqlmodel import RefreshTokenMixin, UserMixin


class RefreshToken(RefreshTokenMixin, table=True):
    pass


class User(UserMixin, table=True):
    tenant_id: str = Field(default="", index=True)
    refresh_tokens: list[RefreshToken] = Relationship()

2. Extend the schemas

The read schema exposes tenant_id and protects it from PATCH /me; the create schema makes it part of registration. Extra create-schema fields are written straight to your model column.

from typing import ClassVar

from fastapi_fullauth import CreateUserSchema, UserSchema


class TenantUser(UserSchema):
    tenant_id: str = ""
    PROTECTED_FIELDS: ClassVar[set[str]] = UserSchema.PROTECTED_FIELDS | {"tenant_id"}


class TenantCreate(CreateUserSchema):
    tenant_id: str

3. Wire the adapter and stamp the claim

from typing import Any

from fastapi_fullauth import FullAuth, FullAuthConfig
from fastapi_fullauth.adapters import SQLModelAdapter


adapter = SQLModelAdapter(
    session_maker=session_maker,
    user_model=User,
    refresh_token_model=RefreshToken,
    user_schema=TenantUser,
    create_user_schema=TenantCreate,
)


async def add_claims(user: TenantUser) -> dict[str, Any]:
    return {"tenant_id": user.tenant_id}


fullauth = FullAuth(
    adapter=adapter,
    config=FullAuthConfig(SECRET_KEY="your-secret-key"),
    on_create_token_claims=add_claims,
)

POST /register now accepts tenant_id alongside email and password, and every issued access token carries the tenant in extra.

4. Read the tenant on each request

from typing import Annotated

from fastapi import Depends
from fastapi_fullauth.dependencies import current_token_payload
from fastapi_fullauth.types import TokenPayload


async def current_tenant(payload: TokenPayload = Depends(current_token_payload)) -> str:
    return payload.extra["tenant_id"]


CurrentTenant = Annotated[str, Depends(current_tenant)]


@app.get("/dashboard")
async def dashboard(tenant: CurrentTenant):
    return {"tenant": tenant}

Because the tenant comes from the signed token, there's no database hit per request. Note the staleness window: a tenant change is picked up on the next token refresh, not instantly.

Log in with a username instead of email

Email stays the account identifier (it's what verification and password reset use), but users sign in with a username.

1. Add a username column and create field

from sqlmodel import Field, Relationship

from fastapi_fullauth import CreateUserSchema
from fastapi_fullauth.models.sqlmodel import RefreshTokenMixin, UserMixin


class RefreshToken(RefreshTokenMixin, table=True):
    pass


class User(UserMixin, table=True):
    username: str = Field(unique=True, index=True)
    refresh_tokens: list[RefreshToken] = Relationship()


class UsernameCreate(CreateUserSchema):
    username: str

2. Point login at the username field

from fastapi_fullauth import FullAuth, FullAuthConfig
from fastapi_fullauth.adapters import SQLModelAdapter


adapter = SQLModelAdapter(
    session_maker=session_maker,
    user_model=User,
    refresh_token_model=RefreshToken,
    create_user_schema=UsernameCreate,
)

fullauth = FullAuth(
    adapter=adapter,
    config=FullAuthConfig(SECRET_KEY="your-secret-key", LOGIN_FIELD="username"),
)

POST /register takes email, password, and username; POST /login now takes username and password. The built-in adapters resolve any model column, so no adapter code is needed. On a custom adapter, override get_user_by_field to handle the new field.

More

  • Send verification and reset emails - register send_verification_email / send_password_reset_email hooks.
  • Enforce password strength - configure a PasswordValidator.
  • Use a non-SQL database - implement the adapter interface.
  • Cookie-based SPA auth - switch to the cookie backend in Frontend integration.