Configuration
Litestar Users enables you to set up pre-configured authentication and user management route handlers in minutes.
The LitestarUsersPlugin accepts a config object in the form of LitestarUsersConfig. The config requires database models, DTOs, a user service and one or more route handler configs.
Minimal example
A minimal example with registration, verification and login facilities:
from dataclasses import dataclass
from typing import Any
import uvicorn
from advanced_alchemy.base import UUIDBase
from advanced_alchemy.extensions.litestar.dto import SQLAlchemyDTO, SQLAlchemyDTOConfig
from advanced_alchemy.extensions.litestar.plugins import (
SQLAlchemyAsyncConfig,
SQLAlchemyInitPlugin,
)
from advanced_alchemy.repository import SQLAlchemyAsyncRepository
from litestar import Litestar
from litestar.dto import DataclassDTO
from litestar.middleware.session.server_side import ServerSideSessionConfig
from litestar_users import LitestarUsersConfig, LitestarUsersPlugin
from litestar_users.config import (
AuthHandlerConfig,
RegisterHandlerConfig,
VerificationHandlerConfig,
)
from litestar_users.mixins import SQLAlchemyUserMixin
from litestar_users.service import BaseUserService
ENCODING_SECRET = "1234567890abcdef" # noqa: S105
DATABASE_URL = "sqlite+aiosqlite:///"
class User(UUIDBase, SQLAlchemyUserMixin):
"""User model."""
class UserRepository(SQLAlchemyAsyncRepository[User]):
model_type = User
@dataclass
class UserRegistrationSchema:
email: str
password: str
class UserRegistrationDTO(DataclassDTO[UserRegistrationSchema]):
"""User registration DTO."""
class UserReadDTO(SQLAlchemyDTO[User]):
config = SQLAlchemyDTOConfig(exclude={"password_hash"})
class UserUpdateDTO(SQLAlchemyDTO[User]):
config = SQLAlchemyDTOConfig(exclude={"password_hash"}, partial=True)
class UserService(BaseUserService[User, Any, Any]): # type: ignore[type-var]
async def post_registration_hook(self, user: User, request: Any = None) -> None:
print(f"User <{user.email}> has registered!")
sqlalchemy_config = SQLAlchemyAsyncConfig(
connection_string=DATABASE_URL,
session_dependency_key="session",
before_send_handler="autocommit",
)
litestar_users = LitestarUsersPlugin(
config=LitestarUsersConfig(
auth_config=ServerSideSessionConfig(),
secret=ENCODING_SECRET,
user_repository_class=UserRepository,
user_read_dto=UserReadDTO,
user_registration_dto=UserRegistrationDTO,
user_update_dto=UserUpdateDTO,
user_service_class=UserService, # pyright: ignore
auth_handler_config=AuthHandlerConfig(),
register_handler_config=RegisterHandlerConfig(),
verification_handler_config=VerificationHandlerConfig(),
)
)
app = Litestar(
plugins=[SQLAlchemyInitPlugin(config=sqlalchemy_config), litestar_users],
route_handlers=[],
)
if __name__ == "__main__":
uvicorn.run(app="basic:app", reload=True)
Authentication backends
The auth_config parameter accepts one of:
| Config class | Backend |
|---|---|
ServerSideSessionConfig / CookieBackendConfig |
Session-based (cookie) |
JWTAuthConfig |
Stateless JWT bearer token |
JWTCookieAuthConfig |
JWT stored in an HttpOnly cookie |
from litestar_users.config import JWTAuthConfig
# Pass JWTAuthConfig() as auth_config; all other LitestarUsersConfig fields are unchanged
auth_config = JWTAuthConfig()
Warning
Set SQLAlchemyAsyncConfig.before_send_handler to "autocommit" to ensure database transactions are committed atomically at the end of the request/response lifecycle. If an error is raised the transaction is rolled back automatically.
Alternatively, you may set LitestarUsersConfig.auto_commit_transactions to True, but this commits immediately after each service call (e.g. UserService.register). If a subsequent post_registration_hook raises an exception the user will have already been persisted, resulting in a confusing duplicate-registration error on the next attempt.
Note
Aside from the pre-configured public routes provided by Litestar-Users, all routes on your application require authentication unless excluded via LitestarUsersConfig.auth_exclude_paths.
Note
Litestar-Users requires the use of a corresponding Litestar plugin for database management.
Anonymous users
By default every route (except those in auth_exclude_paths) requires a valid session or token. If you need a route to be accessible to unauthenticated callers without excluding it globally, Litestar-Users provides AnonymousUser and no_validation.
Declare current_user as a union that includes AnonymousUser on any handler that should accept unauthenticated requests. The middleware will set request.user to an AnonymousUser instance instead of raising a 401, and you can distinguish the two cases with isinstance:
from typing import Annotated
from litestar import get
from litestar_users import AnonymousUser, no_validation
from .models import User
@get("/feed")
async def feed(
current_user: Annotated[User | AnonymousUser, no_validation],
) -> list[str]:
if isinstance(current_user, AnonymousUser):
return ["public item 1", "public item 2"]
return [
"public item 1",
"public item 2",
f"personalized item for {current_user.email}",
]
AnonymousUser exposes the same base attributes as a real user (id, is_active, is_verified, roles, oauth_accounts) with safe sentinel defaults, so code that inspects those fields does not need special-casing.
Note
The no_validation annotation is required because msgspec cannot coerce a union of two custom types. Without it Litestar will raise a validation error when it tries to deserialise the dependency. It is simply Dependency(skip_validation=True) - you can use that directly if you prefer not to import no_validation.