← назад к разделу

Почти каждый реальный сервис должен знать, кто к нему обращается, и решать — разрешить или нет. Во FastAPI для этого нет отдельной магической подсистемы. Аутентификация и авторизация строятся на том же механизме, что и всё остальное — на зависимостях. «Текущий пользователь» — это зависимость. «Требуется роль» — тоже зависимость.

Аутентификация и авторизация — в чём разница

Два слова, которые часто путают:

  • Аутентификация — подтверждение личности. Кто ты? Сервер проверяет токен или пароль и устанавливает, кто обращается.
  • Авторизация — проверка прав. Что тебе можно? Сервер проверяет, имеет ли этот пользователь доступ к конкретному ресурсу.

Сначала идёт аутентификация, потом — авторизация. Нельзя проверить права, не зная, кто перед тобой.

Как устроен типичный поток

Самая распространённая схема для API:

  1. Клиент отправляет логин и пароль на специальный эндпоинт (/auth/token).
  2. Сервер проверяет данные и возвращает JWT-токен — подписанную строку.
  3. При каждом запросе клиент передаёт этот токен в заголовке: Authorization: Bearer <токен>.
  4. Сервер проверяет подпись токена и извлекает из него данные о пользователе.

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 — как переопределить зависимость безопасности в тестах.