User Model¶
UserInDB¶
The full storage model. Never returned from the API directly — always projected through UserRead via .to_read().
class UserInDB(UserBase):
model_config = ConfigDict(from_attributes=True, extra="allow")
id: str
hashed_password: str | None = None # None for OAuth-only accounts
phone_number: str | None = None
phone_verified: bool = False
is_active: bool = True
is_verified: bool = False
is_superuser: bool = False
roles: list[str] = []
scopes: list[str] = []
extra_data: dict[str, Any] = {}
mfa_enabled: bool = False
mfa_secret: str | None = None
backup_codes: list[str] = [] # argon2 hashes, never plaintext
failed_login_attempts: int = 0
locked_until: datetime | None = None
# ... and the verification/reset OTP + token-hash fields
(UserBase provides email, username, full_name.)
Extending it¶
Option 1 — extra_data, for simple cases¶
No subclassing needed. extra="allow" means any extra kwarg gets stored automatically:
user = UserInDB(email="a@example.com", extra_data={"company_id": "acme-1", "plan": "pro"})
Option 2 — full subclassing, for typed fields¶
class MyUser(UserInDB):
company_id: str | None = None
subscription_tier: str = "free"
onboarding_complete: bool = False
Your AbstractUserStore implementation just returns MyUser instances instead of UserInDB — nothing else in authwarden needs to know about the subclass, since every internal usage only touches the base fields.
Projecting custom fields to the API response¶
to_read() only returns base UserRead fields by default. Override it if you want custom fields to actually reach the API response:
class MyUserRead(UserRead):
company_id: str | None = None
subscription_tier: str = "free"
class MyUser(UserInDB):
company_id: str | None = None
subscription_tier: str = "free"
def to_read(self) -> MyUserRead:
base = super().to_read().model_dump()
return MyUserRead(**base, company_id=self.company_id, subscription_tier=self.subscription_tier)
UserCreate¶
The /auth/register request body:
class UserCreate(UserBase):
password: str
phone_number: str | None = None
UserRead¶
The public-safe projection returned by every endpoint — never includes hashed_password, MFA secrets, backup codes, or internal reset/verification state.
class UserRead(UserBase):
id: str
is_active: bool
is_verified: bool
is_superuser: bool
roles: list[str]
scopes: list[str]
mfa_enabled: bool
created_at: datetime
updated_at: datetime