Авторизация в FastAPI: сравниваем четыре способа
В FastAPI нет встроенной системы авторизации «из коробки» — фреймворк даёт примитивы, а собирать из них решение приходится самостоятельно. Разбираем четыре подхода: от простейшего API-ключа до OAuth2-провайдера, с конкретными фрагментами кода и таблицей, чтобы выбрать нужный за пять минут.

Почему «просто добавить авторизацию» не работает
FastAPI (0.110+, Starlette 0.36+) предоставляет модуль fastapi.security с классами HTTPBasic, HTTPBearer, OAuth2PasswordBearer, APIKeyHeader и несколькими другими. Каждый из них — обёртка над зависимостью (Depends), которая извлекает токен или ключ из запроса. Ни один класс не проверяет пароль, не ходит в базу и не выдаёт JWT. Это остаётся на вас.
Отсюда главная ловушка: разработчик копирует пример из документации, видит Swagger-замок и считает, что авторизация готова. На деле замок — всего лишь UI-подсказка; валидации за ним может не быть вовсе. Ниже — четыре реальных способа закрыть эндпоинты, каждый со своей ценой.
Способ 1. API-ключ в заголовке
Самый быстрый вариант для внутренних сервисов и интеграций, где клиент — не человек, а другой бэкенд.
Как устроен
Класс APIKeyHeader достаёт значение из указанного заголовка. Вы сравниваете его с эталоном — всё.
# FastAPI 0.110+, Python 3.11+
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import APIKeyHeader
app = FastAPI()
# Имя заголовка, в котором клиент передаёт ключ
api_key_header = APIKeyHeader(name="X-API-Key")
# В продакшене храните ключ в переменной окружения, не в коде
EXPECTED_KEY = "YOUR_API_KEY"
def verify_api_key(key: str = Depends(api_key_header)) -> str:
if key != EXPECTED_KEY:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Неверный API-ключ",
)
return key
@app.get("/internal/stats", dependencies=[Depends(verify_api_key)])
def get_stats():
return {"orders_today": 142}
Проверка: отправьте запрос без заголовка — Swagger вернёт 403. С правильным ключом — 200.
Чем платите
- Ключ статичен: компрометация = полный доступ до ротации.
- Нет понятия «пользователь» — нельзя разграничить права внутри одного ключа.
- Ключ летит в каждом запросе; если канал не TLS — его перехватят.
Способ 2. HTTP Basic
Годится для админ-панелей и быстрых прототипов, где число пользователей — единицы.
Как устроен
Класс HTTPBasic парсит заголовок Authorization: Basic <base64> и возвращает объект HTTPBasicCredentials с полями username и password.
# FastAPI 0.110+, Python 3.11+
import secrets
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
security = HTTPBasic()
# Эталонные значения — в продакшене берите из хранилища секретов
CORRECT_USER = "admin"
CORRECT_PASS = "YOUR_ADMIN_PASSWORD"
def verify_basic(creds: HTTPBasicCredentials = Depends(security)) -> str:
# compare_digest защищает от атаки по времени
user_ok = secrets.compare_digest(creds.username, CORRECT_USER)
pass_ok = secrets.compare_digest(creds.password, CORRECT_PASS)
if not (user_ok and pass_ok):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверные учётные данные",
headers={"WWW-Authenticate": "Basic"},
)
return creds.username
@app.get("/admin/dashboard")
def dashboard(user: str = Depends(verify_basic)):
return {"message": f"Привет, {user}"}
Важно: secrets.compare_digest вместо ==. Обычное сравнение строк уязвимо к тайминг-атаке — время ответа зависит от количества совпавших символов.
Чем платите
- Пароль передаётся в каждом запросе (base64 — не шифрование). Без TLS бессмысленно.
- Нет механизма истечения сессии — браузер кеширует пару логин/пароль до закрытия вкладки.
- Масштабировать на тысячи пользователей неудобно: нужна база, хеширование, и Basic Auth теряет своё единственное преимущество — простоту.
Способ 3. JWT через OAuth2PasswordBearer
Стандарт де-факто для SPA и мобильных клиентов: сервер выдаёт подписанный токен, клиент шлёт его в заголовке Authorization: Bearer <token>.
Как устроен
OAuth2PasswordBearer — зависимость, которая извлекает токен из заголовка. Верификацию подписи и срока жизни пишете сами, обычно через библиотеку PyJWT (пакет PyJWT, импорт — import jwt) или python-jose.
# FastAPI 0.110+, PyJWT 2.8+, Python 3.11+
from datetime import datetime, timedelta, timezone
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
import jwt
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
# В продакшене — из переменной окружения, длинный, случайный
SECRET = "YOUR_JWT_SECRET"
ALGORITHM = "HS256"
TOKEN_LIFETIME = timedelta(minutes=30)
def create_token(subject: str) -> str:
payload = {
"sub": subject,
"exp": datetime.now(timezone.utc) + TOKEN_LIFETIME,
}
return jwt.encode(payload, SECRET, algorithm=ALGORITHM)
def get_current_user(token: str = Depends(oauth2_scheme)) -> str:
try:
payload = jwt.decode(token, SECRET, algorithms=[ALGORITHM])
username: str = payload["sub"]
except (jwt.ExpiredSignatureError, jwt.InvalidTokenError, KeyError):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Токен невалиден или истёк",
headers={"WWW-Authenticate": "Bearer"},
)
return username
@app.post("/auth/token")
def login(form: OAuth2PasswordRequestForm = Depends()):
# Здесь — проверка пользователя по базе. Пример упрощён.
if form.username != "alice" or form.password != "YOUR_USER_PASSWORD":
raise HTTPException(status_code=400, detail="Неверный логин или пароль")
return {"access_token": create_token(form.username), "token_type": "bearer"}
@app.get("/me")
def me(user: str = Depends(get_current_user)):
return {"user": user}
Чем платите
- Отозвать выданный JWT до истечения
expнельзя без дополнительного хранилища (blocklist в Redis, например). Это часто забывают. - Секрет для подписи — единственная точка отказа. Утёк — все токены скомпрометированы.
- Refresh-токены, ротация, хранение на клиенте — каждый из этих вопросов добавляет код и поверхность атаки.
JWT удобен тем, что бэкенд не хранит состояние сессии. Но именно из-за этого отзыв токена — отдельная инженерная задача, а не настройка в конфиге.
Способ 4. Внешний OAuth2 / OpenID Connect провайдер
Вы не проверяете пароль сами — это делает Google, Keycloak, Auth0 или аналог. Сервер получает ID-токен или access-токен и валидирует подпись по публичному ключу провайдера (JWKS).
Когда это оправдано
- Пользователей много и они уже имеют аккаунт у провайдера (Google Workspace, корпоративный SSO).
- Нужны MFA, управление сессиями и аудит, которые не хочется писать самостоятельно.
- Несколько сервисов должны доверять одному центру авторизации.
Чем платите
- Зависимость от внешнего сервиса: если провайдер лежит, ваши пользователи не входят.
- Сложность отладки: три стороны (клиент, ваш бэкенд, провайдер) обмениваются редиректами и токенами.
- Для self-hosted провайдеров (Keycloak) — дополнительная инфраструктура, обновления, резервное копирование.
Код здесь зависит от конкретного провайдера и клиентской библиотеки. Универсального фрагмента, который можно скопировать и запустить, не существует — обратитесь к документации выбранного провайдера и пакета (например, authlib для FastAPI).
Сравнение подходов
| Критерий | API-ключ | HTTP Basic | JWT (свой) | Внешний провайдер |
|---|---|---|---|---|
| Время до рабочего прототипа | 10 минут | 15 минут | 1–2 часа | 4–8 часов |
| Понятие «пользователь» | Нет | Да, примитивное | Да | Да, с профилем |
| Гранулярные роли | Нет | Только вручную | Через claims | Через scopes/roles |
| Отзыв доступа | Ротация ключа | Смена пароля | Blocklist или короткий TTL | На стороне провайдера |
| Хранение состояния на сервере | Нет | Нет | Нет (без blocklist) | Нет |
| Главный риск | Утечка ключа | Пароль в каждом запросе | Невозможность отзыва | Зависимость от провайдера |
Когда что выбирать
Решение зависит от двух переменных: кто клиент и сколько пользователей.
- Сервис-к-сервису, фиксированный набор клиентов → API-ключ. Ротируйте ключи по расписанию, храните в менеджере секретов.
- Админка на пять человек, MVP → HTTP Basic. Но только через TLS, и с планом миграции на JWT, когда аудитория вырастет.
- SPA или мобильное приложение, десятки–тысячи пользователей → JWT. Закладывайте refresh-токен и blocklist сразу — потом добавлять больнее.
- Корпоративный SSO, несколько сервисов, требования к MFA → Внешний провайдер. Если нет бюджета на SaaS (Auth0, Clerk) — Keycloak, но готовьтесь его обслуживать.
Типичные ошибки
- Проверка подписи отключена. Вызов
jwt.decodeбез указания алгоритма или сoptions={"verify_signature": False}встречается в туториалах «для простоты». В продакшене это означает, что любой может подделать токен. - Секрет в репозитории. Даже если репозиторий приватный. Используйте переменные окружения или менеджер секретов.
- Нет обработки
ExpiredSignatureError. Клиент получает500вместо понятного401с подсказкой обновить токен. - Один токен без срока жизни.
expне задан — токен вечен. Компрометация одного токена = постоянный доступ. - Авторизация есть, а авторизации по ролям нет. Все аутентифицированные пользователи видят все эндпоинты. Аутентификация (кто вы) и авторизация (что вам можно) — разные задачи; вторая часто остаётся «на потом» навсегда.
Примечание: аутентификация — подтверждение личности (логин/пароль, токен). Авторизация — проверка прав на конкретное действие (роль admin, scope write:orders). FastAPI не разделяет эти понятия на уровне API, но в вашем коде они должны быть разделены.
Что делать дальше
Выберите подход по таблице выше, реализуйте аутентификацию, затем сразу добавьте слой авторизации — хотя бы примитивный (поле role в JWT-claims и зависимость require_role). Проверьте, что тесты покрывают три сценария: запрос без токена, запрос с невалидным токеном, запрос с токеном без нужной роли. После этого — настройте CORS, rate limiting и логирование неудачных попыток входа. Всё это не часть авторизации, но без них она не имеет смысла.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: FastAPI Security — официальная документация, PyJWT — документация библиотеки
Частые вопросы
Можно ли использовать JWT без библиотеки PyJWT или python-jose?
Как отозвать JWT до истечения срока?
OAuth2PasswordBearer в FastAPI — это полная реализация OAuth2?
Что если я использую другую версию FastAPI и код не работает?
Где хранить JWT на клиенте — в localStorage или в cookie?
Есть ли подводные камни с CORS при использовании Bearer-токена?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.