Авторизация FastAPI: где заканчивается Bearer и начинается проверка прав
Защищённый маршрут принимает Authorization: Bearer ..., но команда всё равно доступна обычному пользователю. Извлечение Bearer-строки часто ошибочно принимают за готовую авторизацию. На деле серверу нужны три последовательных решения: получить credential, проверить его и разрешить конкретное действие.
Постройте цепочку небольших зависимостей
HTTPBearer разбирает транспортный заголовок, но не проверяет подпись JWT и не ищет пользователя. Следующая зависимость должна проверить credential и вернуть текущего пользователя. Затем отдельная проверка прав решает, можно ли выполнить операцию. Такой порядок позволяет нескольким маршрутам переиспользовать аутентификацию и иметь разные разрешения.
В примере реестр двух тестовых opaque-токенов заменяет выдачу и хранение сессий. Это прямо выбранный транспортный стенд: здесь нет JWT-декодирования и его не следует называть проверкой JWT. Для отсутствующего или неизвестного токена вернём 401 и WWW-Authenticate: Bearer. Для известного пользователя без нужной роли — 403.
Проверим четыре сценария доступа
Нужны FastAPI и HTTPX. auto_error=False позволяет самостоятельно выбрать стабильный договор 401 независимо от поведения готовой security-зависимости при отсутствии заголовка. В production значения credentials должны проверяться выбранной системой сессий или криптографической проверкой токена.
Сохраните весь блок в файл и запустите Python 3.11+ в отдельном окружении с пакетами fastapi, httpx. Для моделей используется Pydantic v2; отдельный сервер для TestClient не нужен.
from typing import Annotated
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.testclient import TestClient
app = FastAPI()
bearer = HTTPBearer(auto_error=False)
users = {"test-reader": {"id": 1, "role": "reader"},
"test-admin": {"id": 2, "role": "admin"}}
def current_user(credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(bearer)]):
user = users.get(credentials.credentials) if credentials else None
if user is None:
raise HTTPException(401, "authentication required", headers={"WWW-Authenticate": "Bearer"})
return user
def admin(user: Annotated[dict, Depends(current_user)]):
if user["role"] != "admin":
raise HTTPException(403, "admin role required")
return user
@app.get("/admin/report")
def report(user: Annotated[dict, Depends(admin)]):
return {"requested_by": user["id"]}
with TestClient(app) as client:
r = client.get("/admin/report")
assert r.status_code == 401 and r.headers["www-authenticate"] == "Bearer"
assert client.get("/admin/report", headers={"Authorization": "Bearer unknown"}).status_code == 401
assert client.get("/admin/report", headers={"Authorization": "Bearer test-reader"}).status_code == 403
assert client.get("/admin/report", headers={"Authorization": "Bearer test-admin"}).json() == {"requested_by": 2}
print("missing, invalid, forbidden and allowed credentials checked")Что меняется при переходе на JWT
Вместо поиска строки в реестре зависимость проверяет подпись, допустимый алгоритм и срок действия токена, затем получает пользователя. Нельзя просто прочитать JSON payload и довериться role. Проверенный токен тоже не всегда равен актуальному праву: пользователь мог быть заблокирован или потерять доступ к ресурсу. Выберите, какие сведения сервер перечитывает на каждом запросе.
Не передавайте токены в query-параметрах: URL часто оказывается в истории, прокси и журналах. При работе через браузер отдельно выберите способ хранения и защиту от соответствующих угроз. Разбор заголовка здесь объясняет одну конкретную HTTP-границу; политика сессии должна быть частью системы целиком.
Какие регрессии оставить в проекте
Тест только с администратором способен пропустить полное удаление проверки роли. Тест без токена способен пройти даже у сломанного маршрута, который запрещает всем. Вместе четыре сценария показывают, что доступ есть у нужного пользователя и отсутствует у остальных. Добавьте к ним заблокированную учётную запись, если такая политика существует.
Не маскируйте все ошибки сервиса под 401. Если база пользователей недоступна, это серверная неисправность, а не неверный пароль клиента. Обрабатывайте ожидаемый отказ credential отдельно от исключений подключения и внутренних ошибок. Так повторный вход не станет бесполезным ответом на падение базы.
Следующая практика
В открытом уроке о текущем пользователе вы подключите его получение через dependency к учебному API. Это следующий шаг после разделения credential и разрешения: маршрут сможет опираться на результат проверенной авторизации. Откройте бесплатную практику текущего пользователя.
Бесплатная программа FastAPI связывает регистрацию, JWT, текущего пользователя и тесты в одном backend. Продолжите её, чтобы заменить локальный реестр учебного примера реальным сценарием входа.
Продолжить на практике
- FastAPI для начинающих: API с базой данных и тестами ↗
Соберёте свой первый полноценный backend на FastAPI: с базой данных, регистрацией, JWT-авторизацией и автотестами. 34 коротких урока и 140 заданий с проверкой — бесплатно.
