Почти каждый реальный сервис должен знать, кто к нему обращается, и решать — разрешить или нет. Во FastAPI для этого нет отдельной магической подсистемы. Аутентификация и авторизация строятся на том же механизме, что и всё остальное — на зависимостях. «Текущий пользователь» — это зависимость. «Требуется роль» — тоже зависимость.
Аутентификация и авторизация — в чём разница
Два слова, которые часто путают:
- Аутентификация — подтверждение личности. Кто ты? Сервер проверяет токен или пароль и устанавливает, кто обращается.
- Авторизация — проверка прав. Что тебе можно? Сервер проверяет, имеет ли этот пользователь доступ к конкретному ресурсу.
Сначала идёт аутентификация, потом — авторизация. Нельзя проверить права, не зная, кто перед тобой.
Как устроен типичный поток
Самая распространённая схема для API:
- Клиент отправляет логин и пароль на специальный эндпоинт (
/auth/token). - Сервер проверяет данные и возвращает JWT-токен — подписанную строку.
- При каждом запросе клиент передаёт этот токен в заголовке:
Authorization: Bearer <токен>. - Сервер проверяет подпись токена и извлекает из него данные о пользователе.
FastAPI понимает этот поток и помогает его реализовать.
OAuth2PasswordBearer — как FastAPI достаёт токен
FastAPI предоставляет готовый инструмент для извлечения токена из заголовка Authorization: Bearer ... — OAuth2PasswordBearer. Он сам по себе является зависимостью: достаёт строку токена и передаёт её дальше.
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token")
Параметр tokenUrl указывает адрес эндпоинта, где клиент получает токен. FastAPI использует это только для документации (Swagger будет знать, куда вести за токеном).
JWT — что это такое и как его создать
JWT (JSON Web Token) — это подписанная строка из трёх частей: заголовка, полезной нагрузки и подписи. В полезной нагрузке лежат данные о пользователе и срок жизни токена. Сервер проверяет подпись — если она верна, данным можно доверять.
Для работы с JWT берут библиотеку PyJWT:
from datetime import datetime, timedelta, timezone
import jwt
def create_access_token(user_id: int, secret: str) -> str:
payload = {
"sub": str(user_id),
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
}
return jwt.encode(payload, secret, algorithm="HS256")
sub(subject) — кто пользователь.exp(expiration) — когда токен истекает;jwt.decodeпроверит это автоматически.- Секрет берётся из настроек приложения, не из кода напрямую.
Текущий пользователь как зависимость
Проверка токена и загрузка пользователя оформляются в одну зависимость. Затем эту зависимость требуют эндпоинты — и они автоматически становятся защищёнными.
from typing import Annotated
from fastapi import Depends, HTTPException
async def get_current_user(
token: Annotated[str, Depends(oauth2_scheme)],
settings: SettingsDep,
users: UserRepositoryDep,
) -> User:
try:
payload = jwt.decode(token, settings.jwt_secret, algorithms=["HS256"])
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="invalid token")
user = await users.find(int(payload["sub"]))
if user is None:
raise HTTPException(status_code=401, detail="invalid token")
return user
CurrentUser = Annotated[User, Depends(get_current_user)]
jwt.decode проверяет подпись и срок жизни. Если токен неверный или просроченный — поднимается InvalidTokenError, который превращается в ответ 401.
Теперь любой эндпоинт может потребовать CurrentUser — и он будет защищён без единой лишней строки про токены:
@router.get("/me", response_model=UserResponse)
async def me(user: CurrentUser):
return UserResponse.from_domain(user)
Проверка прав через зависимости-guards
Пользователь установлен — теперь проверяем, можно ли ему именно это. Авторизация — это тоже зависимость, которая смотрит на роль или свойство пользователя и бросает 403, если доступ запрещён.
def require_admin(user: CurrentUser) -> User:
if not user.is_admin:
raise HTTPException(status_code=403, detail="forbidden")
return user
AdminUser = Annotated[User, Depends(require_admin)]
@router.delete("/{product_id}", status_code=204)
async def delete_product(product_id: int, admin: AdminUser):
...
require_admin зависит от get_current_user — FastAPI сам выстраивает цепочку: токен есть → пользователь найден → пользователь является администратором. Проверка прав происходит до входа в тело эндпоинта.
Scopes — гранулярные права в токене
Когда прав много и они разные у разных клиентов, вместо набора отдельных зависимостей используют scopes — права, зашитые прямо в токен. Например, токен может содержать products:read products:write, а эндпоинт явно требует products:write.
OAuth2PasswordBearer объявляет доступные scopes, эндпоинт указывает нужный через Security(...), а зависимость сверяет, что требуемый scope есть в токене. Для небольшого сервиса с несколькими ролями достаточно guard-зависимостей. Scopes оправданы, когда прав много и они выдаются гранулярно разным клиентам.
Что не делать
Несколько правил, нарушение которых сводит на нет всё остальное:
- Не хранить секреты в коде. JWT-секрет и ключи — только в переменных окружения, через настройки приложения.
- Не доверять телу запроса для авторизации. Поле
is_admin: trueв JSON-теле — не доказательство. Данные для авторизации должны исходить от сервера (из токена или базы данных). - Проверять права на сервере. Скрыть кнопку на клиенте — не защита. Сервер всегда проверяет сам.
- Давать токену конечный срок жизни. Бессрочный токен невозможно отозвать при утечке.
Коротко
- Аутентификация — кто ты; авторизация — что тебе можно. Сначала первое, потом второе.
OAuth2PasswordBearer— зависимость, которая достаёт токен из заголовкаAuthorization: Bearer.- JWT — подписанная строка с данными о пользователе и сроком жизни. Подпись проверяется при каждом запросе.
- Текущий пользователь — зависимость
get_current_user; эндпоинт получает его черезCurrentUser. - Авторизация — ещё одна зависимость (guard), проверяющая роль и бросающая
403. - Права в токене (scopes) удобны, когда клиентов много с разными разрешениями.
- Секреты — только из окружения;
is_adminиз тела запроса — не доказательство.
Что почитать дальше
- Зависимости во FastAPI — как устроен механизм Depends, на котором держится вся безопасность.
- Конфигурация и структура приложения — где хранить JWT-секрет и другие настройки.
- Тестирование FastAPI — как переопределить зависимость безопасности в тестах.