Войти и получить сессию#
Управляемая сессия выполняет login перед первым защищённым вызовом. Приложение передаёт
LoginCredentials, а LoginService вызывает обычную объявленную операцию и возвращает
UserSession.
→ Запрос · POST /auth/login
{"email": "ada@mail.example", "password": "correct-horse"}
← Ответ · 200 OK
{
"access_token": "access-1",
"refresh_token": "refresh-1",
"expires_at": "2099-01-01T00:00:00Z"
}
Модель отмечает роль каждого поля сессии. session_scheme(...) создаёт схему, которую можно
поставить в security роутера.
# examples/docs/auth_lifecycle.py
class LoginCredentials(BaseModel):
email: str
password: SecretStr
class UserSession(BaseModel):
access_token: Annotated[SecretStr, Bearer()]
refresh_token: Annotated[SecretStr, RefreshToken()]
expires_at: Annotated[datetime, ExpiresAt()]
class Account(BaseModel):
id: str
email: str
USER_SESSION = session_scheme(UserSession, name="mail-session")
Login и refresh остаются обычными публичными операциями, а защищённый роутер использует
USER_SESSION по умолчанию.
# examples/docs/auth_lifecycle.py
@dataclass(frozen=True, slots=True, kw_only=True)
class Login(HttpOperation[UserSession]):
__http__ = Http.post("/auth/login", security=None)
email: JsonField[str]
password: JsonField[str]
@dataclass(frozen=True, slots=True, kw_only=True)
class Refresh(HttpOperation[UserSession]):
__http__ = Http.post("/auth/refresh", security=None)
refresh_token: JsonField[str]
@dataclass(frozen=True, slots=True, kw_only=True)
class GetAccount(HttpOperation[Account]):
__http__ = Http.get("/account", errors={401: SessionRejected})
class AuthApi(AsyncApi):
login = op(Login)
refresh = op(Refresh)
class AccountApi(AsyncApi):
security = USER_SESSION
get_account = op(GetAccount)
acquire(...) получает типизированный context.sdk. Корень объявляет обе группы один раз.
# examples/docs/auth_lifecycle.py
class LoginService:
async def acquire(
self,
credentials: LoginCredentials,
context: AuthContext[MailSdk],
) -> UserSession:
return await context.sdk.auth.login(
email=credentials.email,
password=credentials.password.get_secret_value(),
)
async def refresh(
self,
session: UserSession,
context: AuthContext[MailSdk],
) -> UserSession:
return await context.sdk.auth.refresh(
refresh_token=session.refresh_token.get_secret_value(),
)
class MailSdk(AsyncRoot):
auth = api_group(AuthApi)
account = api_group(AccountApi)
Первый защищённый вызов запускает acquire(...), сохраняет результат и повторно планирует
операцию уже с Authorization.
# examples/docs/auth_login.py
async def main() -> None:
site = AuthSite()
credentials = LoginCredentials(
email="ada@mail.example",
password=SecretStr("correct-horse"),
)
async with mail_sdk(site, credentials=credentials) as sdk:
account = await sdk.account.get_account()
print(f"account: {account.id} {account.email}")
print(f"first request: {site.calls[0]}")
Браузерный вход начинается с реальной HTML-формы, а пошаговые операции BrowserLogin разобраны
в главе учебного примера.
<!-- examples/mail/site/app.py -->
<main data-page="identify">
<form method="post" action="/login/identify">
<label>Почта <input name="email" type="email"></label>
<button type="submit">Продолжить</button>
</form>
</main>
Ответ login |
Правило |
Что получает вызывающий |
|---|---|---|
|
|
Результат защищённого метода |
Отображённый |
Сессия не сохранена |
Типизированное исключение |
Неизвестный или повреждённый ответ |
Разбор модели не выполнен |
Ошибка протокола |
Запускаемый файл целиком
"""Acquire a session automatically before a protected request."""
import asyncio
from pydantic import SecretStr
from examples.docs.auth_lifecycle import AuthSite, LoginCredentials, mail_sdk
# region docs: auth-login-run
# examples/docs/auth_login.py
async def main() -> None:
site = AuthSite()
credentials = LoginCredentials(
email="ada@mail.example",
password=SecretStr("correct-horse"),
)
async with mail_sdk(site, credentials=credentials) as sdk:
account = await sdk.account.get_account()
print(f"account: {account.id} {account.email}")
print(f"first request: {site.calls[0]}")
# endregion docs: auth-login-run
if __name__ == "__main__":
asyncio.run(main())
account: user-42 ada@mail.example
first request: POST /auth/login
session_auth(...) остаётся короткой формой для SDK без общей схемы роутера. В корне с
несколькими группами используйте session_scheme(...).configure(...), как в этом примере.