сказать
Виды авторизации Важность, зачем это испольуется и где можно встретить Что было бы круто уже знать перед просмотром видео. Если ты не знаешь чегото, то после просмотра изучишь, я постараюсь объяснять максимально просто Возможно тебе потребуется вернуться к этому видео несколько раз, чтобы лучше понять какую то тему. Сначала посмотри целиком, потом тебе будет проще понять
План:
Терминология
Для начала освежим в памяти базовые термины, ими мы будем оперировать дальше

Идентификация (Identification)
Идентификация — это процесс, при котором пользователь представляется системе, то есть заявляет, кто он есть.
Ключевые черты:
- Это заявление личности, а не проверка
- Само по себе не гарантирует, что вы тот, за кого себя выдаёте
- Всегда предшествует аутентификации Используется в:
- Формы логина (
usernameилиemail) - JWT — поле
sub(subject) — это идентификатор
Аутентификация (Authentication)
Аутентификация — это процесс проверки личности пользователя. Система должна убедиться, что вы действительно тот, за кого себя выдаёте.
Ключевые методы:
- Пароль
- Токен / сертификат
- Одноразовый код (OTP)
- Биометрия (лицо, палец)
Авторизация (Authorization)
Авторизация — это процесс определения, какие действия разрешены пользователю после успешной аутентификации.
Ключевые формы:
- Роли (
admin,user,moderator) - Права (
read,write,delete) - Scopes (в OAuth2)
- ACL (access control lists)
🧠 Запомни:
Аутентификация — это “докажи, что ты — это ты”
Авторизация — “что тебе разрешено”
Base64
Base64 — это способ кодировать любые данные (текст или байты) в ASCII-символы, чтобы их можно было безопасно передавать по интернету (в заголовках, URL, JSON и т.п.).
Base64 — это способ упаковать данные в текст. Не защищает, не шифрует, просто делает байты пригодными для HTTP. Как работает:
- Преобразует бинарные данные в набор символов A-Z, a-z, 0-9, + и /.
- Выход всегда строка, пригодная для HTTP и JSON.
- Часто заканчивается на
=(паддинг для выравнивания).
🧑💻 Примеры:
# Кодирование строки:
import base64
text = "hello:world"
encoded = base64.b64encode(text.encode()).decode()
print(encoded) # aGVsbG86d29ybGQ=
# Декодирование:
decoded = base64.b64decode(encoded).decode()
print(decoded) # hello:world
⚠️ Важно:
- Base64 — не шифрование! Любой может декодировать обратно. Это не безопасность, а удобство передачи.
- Для защиты (например, в JWT) используется подпись, а не base64.
HTTP
HTTP (HyperText Transfer Protocol) - протокол передачи данных, изначально предназначенный для передачи гипертекстовых документов. В модели OSI находится на 7, прикладном уровне, в модели TCP/IP - на 4, также прикладном.
Протокол HTTP работает по принципу клиент-сервер. Клиентское приложение формирует запрос и отправляет его на сервер, после чего сервер обрабатывает запрос, формирует ответ и передаёт его обратно клиенту. Структуры запроса и ответа похожи: стартовая строка, заголовки, тело ответа.
- Стартовая строка запроса состоит из метода, пути и версии протокола:
GET /index.html HTTP/1.1Стартовая строка ответа состоит из версии протокола, кода ответа и текстовой расшифровки ответа:HTTP/1.1 200 OK - Заголовки – это набор пар ключ-значение, например,
Content-Type: text/html; charset=utf-8. В заголовках передают метаданные запроса/ответа: язык пользователя, авторизацию, тип контента и т. д. - Тело ответа может быть пустым, либо может передавать текст, файлы, бинарные данные. Тело отделяется от заголовков пустой строкой. По умолчанию HTTP работает на 80 порту. HTTPS (Hypertext Transfer Protocol Secure) - расширение HTTP, в котором данные передаются не просто текстом, а шифруются с помощью TLS. По умолчанию работает на 443 порту. В настоящее время используются версии HTTP/1.1 и HTTP/2. Нововведения HTTP/1.1:
- заголовок
Connection: keep-alive, чтобы не открывать новое TCP соединение для каждого запроса, а пользоваться одним Нововведения HTTP/2: - бинарный формат вместо текстового, полученное сообщение не нужно сначала переводить в текст, а потом парсить, теперь полученное сообщение парсится сразу в бинарном формате - это проще, чем в текстовом
- фрейминг: запросы и ответы состоят из одного или нескольких независимых пакетов - фреймов
- мультиплексирование: используются стримы - последовательности фреймов с одинаковым stream id. В рамках одного соединения может быть несколько стримов, за счет этого уменьшается количество TCP-соединений. Стримам можно задавать приоритет.
- server push - сервер может инициировать отправку ресурсов клиенту без явного запроса, например у нас запросили HTML страницу и мы заранее отсылаем еще и CSS
- браузеры поддерживают HTTP/2 только поверх TLS
HTTP методы
| Метод | Что делает | Пример эндпоинта | Safe | Idempotent |
| POST | отправляет данные на сервер | /photos | No | No |
| PUT | обновляет существующий ресурс, иногда может создавать новый; нужно передать полное представление ресурса | /photos/id | No | Yes |
| PATCH | вносит изменения в конкретный ресурс | /photos/id | No | [No](https://stackoverflow.com/questions/41390997/why-patch-is-neither-safe-nor-idempotent) |
| GET | запрашивает информацию | /photos, /photos/id | Yes | Yes |
| HEAD | то же самое, что и GET, но запрашивает только заголовки без тела ответа | /photos, /photos/id | Yes | Yes |
| DELETE | удаляет ресурс | /photos/id | No | Yes |
| OPTIONS | запрашивает информацию о сервере, в т. ч. о допустимых к использованию HTTP-методах; часто используется в контексте [CORS](/1b114b41f8794920ad87c65a9f95124d?pvs=25) | /photos | Yes | Yes |
| TRACE | используется для тестирования и отладки соединения между клиентом и сервером | /photos | No | Yes |
Hello, World!
This is an example page.
``` ## Примеры кодов ответов - 1xx - информационные - `100 Continue` - запрос успешно принят и клиент может продолжать присылать запросы - `101 Switching Protocol` - присылается в ответ на запрос клиента, содержащий заголовок `Upgrade`, и указывает, что сервер переключился на протокол, который был указан в заголовке - 2хх - успешно - `200 OK` - запрос успешно обработан - `201 Created` - ресурс создан - 3хх - перенаправление - `304 Not Modified` - используется для кэширования, запрошенный ресурс не был изменён, клиент может продолжать использовать кэшированную версию ответа - `307 Temporary Redirect` - временное перенаправление - 4хх - ошибка на стороне клиента - `401 Unauthorized` - для получения запрашиваемого ответа нужна аутентификация - `403 Forbidden` - у клиента нет прав доступа к содержимому - `404 Not Found` - сервер не может найти запрашиваемый ресурс - 5хх - ошибка на стороне сервера - `500 Internal Server Error` - сервер столкнулся с ситуацией, которую он не знает как обработать - `504 Gateway Timeout` - сервер не может получить ответ вовремяМеханизмы аутентификации и авторизации
Ниже перечислены распространённые механизмы. Это не взаимоисключающие альтернативы: например, приложение может использовать OIDC для входа, серверную сессию для браузера и OAuth access token для вызова внешнего API.
- HTTP Basic authentication — простая схема передачи пары логин/пароль в каждом запросе. Допустима только поверх HTTPS и подходит для ограниченных сценариев.
- Session authentication — клиент хранит идентификатор сессии, а сервер — состояние сессии. Часто является хорошим выбором для browser-based приложений.
- Bearer token — клиент предъявляет access token. JWT — один из форматов токена, но не отдельный протокол аутентификации и не универсально «лучший» выбор для API или SPA.
- OAuth 2.0 — framework делегированной авторизации. Для входа пользователя поверх OAuth 2.0 обычно используют OpenID Connect.
- Keycloak — готовый identity and access management server с поддержкой OIDC, OAuth 2.0, SAML, SSO и MFA.
Basic auth
Basic Auth — это самый простой способ аутентификации, при котором логин и пароль передаются в каждом HTTP-запросе в виде base64-кодированной строки.
Basic Auth — это минимализм в чистом виде. Идеально, когда нужно просто и быстро, но никогда не используйте его в публичных приложениях без HTTPS. Это не решение “на годы” — это способ “воткнуть логин за 30 секунд”.
Как работает:
- Пользователь вводит логин и пароль
- Эти данные кодируются в base64:
username:passwordBase64 не шифрует, а просто кодирует → нужен HTTPS - В каждом запросе отправляется заголовок:
Authorization: Basic YWRtaW46c2VjcmV0
- Сервер декодирует заголовок и проверяет логин/пароль вручную или через middleware
Middleware — это промежуточный слой, который обрабатывает запрос до (или после) основного обработчика запроса.
✅ Плюсы:
- Настраивается за 1 минуту (например, в
Nginxили FastAPI) - Не требует куки, базы сессий, генерации токенов
- Стандарт HTTP с 1990-х, реализован во многих библиотеках
❌ Минусы:
- Даже после логина — логин и пароль уходят в каждом запросе
- Управлять logout в браузере сложнее: браузер может повторно использовать credentials до закрытия контекста или очистки сохранённых данных.
- Без HTTPS перехват заголовка раскрывает логин и пароль. HTTPS обязателен.
- Сама схема не определяет роли, scopes, logout или жизненный цикл credentials; при необходимости это реализует приложение.
Где применимо:
✅ Подходит:
- Админ-панели “для своих”
- Внутренние инструменты (за VPN)
- Быстрые MVP, тестовые API ❌ Не подходит:
- Публичные веб-приложения
- SPA и мобильные клиенты
- Продакшн без HTTPS
Пример в FastAPI:
Демо пример GitHub - takentui/authorization_types at fastapi-basic-auth
Документация: HTTP Basic Auth - FastAPI
import secrets
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
security = HTTPBasic()
@app.get("/secret-resource")
def read_secret(credentials: HTTPBasicCredentials = Depends(security)):
username_ok = secrets.compare_digest(credentials.username.encode(), b"admin")
password_ok = secrets.compare_digest(credentials.password.encode(), b"secret")
if not (username_ok and password_ok):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Basic"},
)
return {"message": "Welcome!"}
sequenceDiagram
title Basic Authentication в FastAPI
actor Client as Клиент
participant FastAPI as FastAPI сервер
participant DB as База данных
Client->>+FastAPI: GET /secure-resource
FastAPI-->>Client: 401 Unauthorized<br>WWW-Authenticate: Basic
Client->>FastAPI: GET /secure-resource<br>Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==<br>(username:password в Base64)
FastAPI->>+DB: Проверка логина/пароля
DB-->>-FastAPI: Валидация результата
alt Аутентификация успешна
FastAPI-->>Client: 200 OK<br>+ запрошенные данные
else Аутентификация не удалась
FastAPI-->>Client: 401 Unauthorized
end
deactivate FastAPI
Применение в NGINX
⚠️ Это наиболее частый сценарий использования basic auth
Демо пример GitHub - takentui/authorization_types at nginx-basic-auth
Документация: Restricting Access with HTTP Basic Authentication
Nginx может сам проверять логин и пароль до того, как запрос попадёт в backend (например, FastAPI, Django и т.д.).
Это удобно, когда нужно:
- Защитить dev-сервер, staging или внутренний API
- Поставить “заглушку” на admin-панель
- Не лезть в код приложения вообще
Как работает:
- Пользователь заходит на защищённый URL
- Браузер показывает окно ввода логина/пароля
- Nginx проверяет их с помощью **файла ****
.htpasswd** - Только после этого отдаёт доступ
sequenceDiagram
title Basic Authentication через Nginx с FastAPI сервисами
actor Client as Клиент
participant Nginx as Nginx
participant Service1 as FastAPI Сервис 1
participant Service2 as FastAPI Сервис 2
participant AuthFile as .htpasswd
Client->>+Nginx: GET /service1/resource
Nginx-->>Client: 401 Unauthorized<br>WWW-Authenticate: Basic
Client->>Nginx: GET /service1/resource<br>Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Nginx->>+AuthFile: Проверка логина/пароля
AuthFile-->>-Nginx: Результат валидации
alt Аутентификация успешна
Nginx->>+Service1: GET /resource<br>(внутренний запрос)
Service1-->>-Nginx: 200 OK + данные
Nginx-->>Client: 200 OK + данные
Note over Client,Nginx: Запрос к другому сервису
Client->>+Nginx: GET /service2/resource<br>Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Nginx->>AuthFile: Проверка логина/пароля
AuthFile-->>Nginx: Результат валидации
Nginx->>+Service2: GET /resource<br>(внутренний запрос)
Service2-->>-Nginx: 200 OK + данные
Nginx-->>-Client: 200 OK + данные
else Аутентификация не удалась
Nginx-->>Client: 401 Unauthorized
end
Важно:
- Обязательно использовать HTTPS — иначе логин/пароль летят в открытом виде
- Один и тот же логин/пароль у всех (без логики на стороне приложения)
- Нет сессий, ролей, разграничения доступа — только "допущен или нет"
Session auth
Демо пример github.com
Это старый, проверенный способ аутентификации, который широко используется в классических веб-приложениях (Django, Flask, Rails и др.) Схема работы session based auth
- Пользователь логинится — отправляет логин/пароль
→
POST /loginс{"username": "...", "password": "..."} - Сервер проверяет их и создаёт сессию → Сохраняем в памяти или базе связь сессии с учеткой
- Сервер присваивает сессии уникальный ID (обычно — случайная строка) → Генерируем session_id
- Этот ID отправляется пользователю в cookie → например,
HTTP/1.1 200 OK
Set-Cookie: sessionid=abc123; HttpOnly; Path=/; Secure
- При каждом следующем запросе браузер автоматически отправляет cookie → например
GET /profile HTTP/1.1
Host: example.com
Cookie: sessionid=abc123
- Сервер по ID находит сессию и “узнаёт” пользователя → сверяем сессию в базе и вытаскиваем учетку к которой привязана сессия
sequenceDiagram
title Session Authentication в FastAPI
actor Client as Клиент
participant FastAPI as FastAPI сервер
participant DB as База данных
participant Redis as Redis (хранилище сессий)
Client->>+FastAPI: POST /login<br>{"username": "user", "password": "pass"}
FastAPI->>+DB: Проверка логина/пароля
DB-->>-FastAPI: Валидация результата
alt Аутентификация успешна
Note over FastAPI,Redis: Создание сессии
FastAPI->>+Redis: Сохранение session_id: {user_data}<br>set("session:abc123", user_json, ex=3600)
Redis-->>-FastAPI: OK
FastAPI-->>Client: 200 OK<br>Set-Cookie: session_id=abc123 HttpOnly
Note over Client,FastAPI: Последующие запросы
Client->>+FastAPI: GET /secure-resource<br>Cookie: session_id=abc123
FastAPI->>+Redis: Получение данных сессии<br>get("session:abc123")
Redis-->>-FastAPI: Данные сессии (user_data)
FastAPI-->>-Client: 200 OK + данные
Note over Client,FastAPI: Выход пользователя
Client->>+FastAPI: POST /logout<br>Cookie: session_id=abc123
FastAPI->>+Redis: Удаление сессии<br>del("session:abc123")
Redis-->>-FastAPI: OK
FastAPI-->>-Client: 200 OK<br>Set-Cookie: session_id=abc123,expires=Thu, 01 Jan 1970...
else Аутентификация не удалась
FastAPI-->>Client: 401 Unauthorized
end
✅ Плюсы
- Простота внедрения: легко реализуется на многих фреймворках
- Контроль на сервере: данные сессии хранятся на сервере, а клиент получает только непрозрачный идентификатор сессии.
- Простое управление: можно легко аннулировать сессии, контролировать время жизни
- **Меньшая нагрузка на клиент: **вся логика аутентификации на сервере
- Снижение риска кражи cookie через XSS:
HttpOnlyзапрещает JavaScript читать cookie. Однако XSS-код всё ещё может выполнять действия от имени пользователя, поэтому защита от XSS остаётся обязательной.
❌ Минусы
- **Хранение состояния: **требует хранения сессий на сервере (память/БД/Redis)
- Масштабируемость: сложнее распределять нагрузку между серверами
- Проблемы с CORS: усложняется работа при кросс-доменных запросах
- Уязвимость к CSRF-атакам: требуются дополнительные меры защиты
- **Производительность: **дополнительные запросы к хранилищу сессий
Пример в FastAPI:
Вот короткий пример реализации на FastAPI:
Этот пример показывает базовую реализацию сессионной аутентификации с использованием FastAPI и Redis для хранения сессий. Ключевые моменты:
- Сессионный ID генерируется как
UUIDи хранится вRedisс ограниченным временем жизни - Cookie устанавливается с
HttpOnly,Secureи подходящимSameSite. Для изменяющих состояние запросов всё равно нужна CSRF-защита, соответствующая архитектуре приложения - Реализованы основные эндпоинты: вход, выход и получение информации о текущем пользователе
- При выходе сессия удаляется из
Redisи куки очищаются
import uuid
import redis
from typing import Optional
from fastapi import Depends, FastAPI, HTTPException, Request, Response, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from pwdlib import PasswordHash
from pydantic import BaseModel
app = FastAPI()
security = HTTPBasic()
password_hash = PasswordHash.recommended()
# Подключение к Redis
r = redis.Redis(host='localhost', port=6379, db=0)
SESSION_EXPIRE = 3600 # 1 час
# Модели данных
class User(BaseModel):
username: str
password: str
# Заглушка для базы данных пользователей
fake_users_db = {
"user1": {
"username": "user1",
# Только для демо: в реальном приложении готовый хеш хранится в БД.
"hashed_password": password_hash.hash("password1"),
}
}
# Проверка учетных данных
def get_current_user(credentials: HTTPBasicCredentials = Depends(security)):
user = fake_users_db.get(credentials.username)
if not user or not password_hash.verify(credentials.password, user["hashed_password"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверные учетные данные",
headers={"WWW-Authenticate": "Basic"},
)
return user
# Получение пользователя по сессии
def get_user_by_session(request: Request):
session_id = request.cookies.get("session_id")
if not session_id:
return None
username = r.get(f"session:{session_id}")
if not username:
return None
return fake_users_db.get(username.decode())
# Маршруты
@app.post("/login")
def login(response: Response, user: User = Depends(get_current_user)):
# Создание сессии
session_id = str(uuid.uuid4())
r.setex(f"session:{session_id}", SESSION_EXPIRE, user["username"])
# Установка cookie
response.set_cookie(
key="session_id",
value=session_id,
httponly=True,
max_age=SESSION_EXPIRE,
samesite="lax",
secure=True,
path="/",
)
return {"message": "Успешный вход"}
@app.get("/me")
def read_user(user: Optional[dict] = Depends(get_user_by_session)):
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Требуется аутентификация"
)
return {"username": user["username"]}
@app.post("/logout")
def logout(response: Response, request: Request):
session_id = request.cookies.get("session_id")
if session_id:
r.delete(f"session:{session_id}")
response.delete_cookie(key="session_id")
return {"message": "Выход выполнен успешно"}
Token based auth
Схема работы token-based авторизации (Без refresh token)
- Пользователь отправляет логин и пароль
→
POST /loginс{"username": "...", "password": "..."} - Сервер проверяет учётные данные → Сравнивает логин и пароль с логин-паролем из базы/хранилища.
- Сервер создаёт JWT access token → С помощью функции из пункта 2. По сути генерирует специальную строку и не хранит ее, просто возвращает как результат метода
- Токен возвращается пользователю
→ В теле ответа или
HttpOnlycookie - Клиент сохраняет или удерживает токен
→ Вариант хранения выбирают по модели угроз.
localStorageдоступен JavaScript и особенно чувствителен к XSS.HttpOnlycookie недоступна JavaScript, но автоматически отправляется браузером и требует защиты от CSRF. Cookie не становится безопасной автоматически: важныSecure,HttpOnly, подходящийSameSiteи защита приложения от XSS.
*LocalStorage — это хранилище в браузере, где можно сохранять данные в формате **
ключ:значение*
- Дальнейшие запросы идут с токеном
→ В заголовке:
Authorization: Bearer <token> - Сервер проверяет подпись и
expтокена → Если всё ок — пользователь авторизован - Если exp прошел, значит токен более невалиден.
→ Возвращаемся на пункт 1.

Access token может быть непрозрачной случайной строкой или структурированным токеном, например JWT. Формат выбирает authorization server; resource server не должен предполагать JWT, если это не закреплено контрактом.
JWT
RFC 7519 — стандарт JWT. Сайт JWT.io полезен как отладочный инструмент и каталог библиотек, но не является нормативной спецификацией.
Демо пример GitHub - takentui/authorization_types at jwt-auth
JWT (JSON Web Token) — формат токена. Часто JWT представлен подписанным compact JWS из трёх частей
header.payload.signature; зашифрованный compact JWE состоит из пяти частей. Каждая часть:
- Header — метаданные: тип токена и алгоритм подписи
- Payload — данные (claims): кто вы, срок действия, роли и т.д.
- Signature — цифровая подпись (проверка подлинности)
Bearer access token обычно передаётся в HTTP-заголовке Authorization: Bearer {your_token_here} согласно RFC 6750.
Base64url даёт компактное ASCII-представление, удобное для URL и заголовков, но не шифрует данные и не обеспечивает их конфиденциальность. Payload подписанного JWT обычно можно прочитать без ключа.
Как создаётся JWT
пример токена
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30
Давай для начала рассмотрим какую информацию он может хранить и потом поймем как сделать свой токен.
Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payload:
{
"sub": "1234567890", // user ID
"name": "John Doe", // имя пользователя
"admin": true, // роль пользователя
"iat": 1516239022, // время выпуска: NumericDate
"exp": 1716666000 // время истечения: NumericDate
}
Signature:
JWT может быть подписан или зашифрован в зависимости от применения JOSE. Для bearer access token принимать неподписанный JWT (alg: none) обычно нельзя: resource server должен заранее ограничить допустимые алгоритмы и проверить подпись, issuer, audience, срок действия и другие ожидаемые claims.
a-string-secret-at-least-256-bits-long
Пример генерации токена:
*можешь запустить локально, установив пакет **pyJWT*
import os
from datetime import datetime, timedelta, timezone
import jwt
# Сгенерируйте, например: openssl rand -hex 32.
# Секрет храните вне репозитория — в secret storage или переменной окружения.
SECRET_KEY = os.environ["JWT_SECRET_KEY"]
ALGORITHM = "HS256"
def generate_token(data: dict):
payload = data.copy()
payload["exp"] = datetime.now(timezone.utc) + timedelta(minutes=15)
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
# Пример использования
token = generate_token({"sub": "user_id_123"})
print(token)
🔹** Что кладут в payload? (Так называемые claims)**
| **Claim** | **Назначение** | **Обязательный?** |
| sub | ID пользователя | Зависит от профиля токена |
| exp | Время истечения токена (timestamp) | Зависит от профиля токена |
| iat | Когда был выпущен | ⚠️ Желательно |
| nbf | “Не раньше чем” (not before) | ❌ Опционально |
| iss | Кто выдал токен (issuer) | ❌ Опционально |
| aud | Для кого токен предназначен (audience) | ❌ Опционально |
| Пользовательские | Например, role, email, scopes | ❌ Опционально |
- При авторизации клиент получает два токена:
- Access token (короткоживущий, например 15-30 минут)
- Refresh token (долгоживущий, например 7-30 дней)
- Когда access token истекает:
- Клиент отправляет
refreshtoken на специальный endpoint (например,POST /refresh) - Сервер проверяет валидность
refreshтокена - При успешной проверке сервер генерирует новый
accesstoken и возвращает его клиенту - Опционально: генерирует новый
refreshtoken (rotation strategy)
- Схема запроса обновления:
POST /refresh
Content-Type: application/json
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ..."
}
- Ответ:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC...", // Опционально
"token_type": "Bearer",
"expires_in": 900
}
Особенности refresh токенов:
- Структура: Могут быть JWT или непрозрачными случайными строками с достаточной энтропией.
- Хранение на сервере: Зависит от дизайна authorization server. Для rotation, обнаружения повторного использования и отзыва обычно требуется серверное состояние; секретные токены в хранилище желательно защищать так же тщательно, как другие credentials.
- Хранение на клиенте: Для браузерного клиента часто используют
HttpOnly,Securecookie с подходящимSameSite, но конкретная схема зависит от архитектуры и модели угроз. - Срок жизни: Значительно дольше
accessтокена, но имеет конечный срок
Рекомендации по реализации:
- Создавайте уникальный
refreshtoken для каждого устройства пользователя - Для public clients используйте rotation refresh token или sender-constrained refresh token и обнаруживайте повторное использование старого токена.
Отзыв JWT (revoke)
Одним из недостатков JWT является сложность отзыва токенов, так как они по умолчанию валидны до истечения срока. Существует несколько подходов к решению этой проблемы:
- Blacklist отозванных токенов
# Пример хранения отозванных токенов в Redis
def revoke_token(token):
token_data = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
jti = token_data["jti"] # уникальный ID конкретного токена
ttl = token_data["exp"] - int(datetime.now(timezone.utc).timestamp())
if ttl > 0:
redis_client.setex(f"revoked:{jti}", ttl, "1")
def is_token_revoked(token):
token_data = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
jti = token_data["jti"]
return redis_client.exists(f"revoked:{jti}")
- Контроль через Refresh токены
- Храните только refresh токены в базе данных
- При логауте - удаляйте refresh токен из базы
- Держите короткий срок жизни access токенов
- Выпуск с версией/идентификатором пользовательской сессии
def generate_token(user_id, session_id):
return jwt.encode({
"sub": user_id,
"jti": str(uuid.uuid4()),
"sid": session_id, # Идентификатор сессии
"exp": datetime.now(timezone.utc) + timedelta(minutes=15)
}, SECRET_KEY, algorithm=ALGORITHM)
def verify_token(token, active_sessions):
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
# Проверяем не только подпись, но и актуальность сессии
if payload["sid"] not in active_sessions.get(payload["sub"], []):
raise Exception("Token revoked")
return payload
Схема как работает
sequenceDiagram
title JWT Authentication в FastAPI
actor Client as Клиент
participant FastAPI as FastAPI сервер
participant DB as База данных
participant Redis as Redis (для revoked токенов)
Client->>+FastAPI: POST /login<br>{"username": "user", "password": "pass"}
FastAPI->>+DB: Проверка логина/пароля
DB-->>-FastAPI: Валидация результата
alt Аутентификация успешна
Note over FastAPI: Создание JWT-токенов
FastAPI-->>Client: 200 OK<br>{"access_token": "eyJhbGci...", "refresh_token": "eyJhbGci...", "token_type": "bearer"}
Note over Client,FastAPI: Запрос с access_token
Client->>+FastAPI: GET /secure-resource<br>Authorization: Bearer eyJhbGci...
Note over FastAPI: Проверка JWT подписи<br>и проверка exp/nbf claims
FastAPI->>+Redis: Проверка отозванности<br>exists("revoked:token_jti")
Redis-->>-FastAPI: Результат (0/1)
alt Токен валидный
FastAPI-->>-Client: 200 OK + данные
else Токен невалидный или отозван
FastAPI-->>Client: 401 Unauthorized
end
Note over Client,FastAPI: Обновление токена при истечении срока
Client->>+FastAPI: POST /refresh<br>{"refresh_token": "eyJhbGci..."}
Note over FastAPI: Проверка refresh_token
FastAPI->>+Redis: Проверка отозванности<br>exists("revoked:refresh_token_jti")
Redis-->>-FastAPI: Результат (0/1)
alt Refresh токен валидный
Note over FastAPI: Генерация новых токенов
FastAPI->>+Redis: Добавление старого в черный список<br>setex("revoked:refresh_token_jti", ttl, "1")
Redis-->>-FastAPI: OK
FastAPI-->>-Client: 200 OK<br>{"access_token": "eyJhbGci...", "refresh_token": "eyJhbGci...", "token_type": "bearer"}
else Refresh токен невалидный или отозван
FastAPI-->>Client: 401 Unauthorized
end
Note over Client,FastAPI: Выход пользователя
Client->>+FastAPI: POST /logout<br>Authorization: Bearer eyJhbGci...
FastAPI->>+Redis: Добавление токена в черный список<br>setex("revoked:token_jti", ttl, "1")
Redis-->>-FastAPI: OK
FastAPI-->>-Client: 200 OK
else Аутентификация не удалась
FastAPI-->>Client: 401 Unauthorized
end
✅ Плюсы:
- Stateless - сервер не хранит сессии, что упрощает масштабирование
- Самодостаточность - вся необходимая информация содержится в токене
- Кросс-доменная работа - легко использовать в микросервисной архитектуре
- Производительность - не требуется обращение к БД для проверки авторизации
- Гибкость - можно включать различные claims (роли, права и т.д.)
- Стандартизация - широко используемый формат с большим количеством библиотек
- Разделение ответственности - возможность выделить авторизацию в отдельный сервис
❌ Минусы:
- Размер - JWT обычно больше, чем session ID (особенно с большим количеством claims)
- Сложность отзыва - нет встроенного механизма отзыва (нужна доп. реализация)
- Безопасность хранения - сложно безопасно хранить на клиенте
- Утечка информации - payload не зашифрован, только закодирован в Base64
- Усложнение при использовании refresh токенов - требует хранения состояния
- Риск XSS-атак - при неправильном хранении в localStorage
- Неизменяемость - нельзя изменить данные в токене без перевыпуска
- Истекшие токены - до истечения срока клиент продолжает использовать неактуальный токен
Где хранить токен на клиенте
| Хранилище | Access Token | Refresh Token | Комментарий |
| HttpOnly Cookie | ✅ Хорошо | ✅ Лучший вариант | Защита от XSS, уязвим к CSRF (нужна доп. защита) |
| localStorage | ❌ Небезопасно | ❌ Очень небезопасно | Уязвим к XSS-атакам |
| sessionStorage | ⚠️ Относительно безопасно | ❌ Небезопасно | Живет только во время сессии браузера |
| Memory (JS переменная) | ✅ Хорошо | ⚠️ Не рекомендуется | Исчезает при обновлении страницы |
| IndexedDB | ⚠️ Зависит от реализации | ❌ Небезопасно | Требует шифрования |
🎯 Сам SSO — это поведение, а не технология. А реализуется он через Keycloak, Okta, Azure AD и т.п.
- User (пользователь) — хочет войти, знает свой логин и пароль
- Service Provider (SP) — сайт, в который он заходит (например, GitLab)
- Identity Provider (IdP) — сервис, который подтверждает личность (например, Keycloak, Google, Okta)
Как это выглядит для пользователя:
- Заходишь на
app1.company.com→ логинишься - Переходишь на
app2.company.com→ уже залогинен - Переходишь в
dashboard.partner.com→ тоже залогинен → без ввода пароля
Как это работает под капотом
- Пользователь заходит на
Service A - Его редиректят на IdP
- Он логинится один раз
- IdP возвращает токен или сессию
- Пользователь получает доступ к
Service A - При переходе на
Service B, тот снова обращается к IdP → уже есть сессия → вход без пароля
SSO работает через протоколы:
- SAML — старый, XML, часто в enterprise
- OpenID Connect (OIDC) — надстройка над OAuth2, современный стандарт
- Иногда — Kerberos (в Windows-доменных сетях)
Пример:
Допустим, у тебя есть три приложения:
billing.domain.comdocs.domain.comadmin.domain.comС SSO:- Все они доверяют одному IdP (например, Keycloak)
- Пользователь логинится один раз — и получает доступ ко всем трём
✅ Плюсы SSO
- Удобно для пользователей (1 логин → много доступов)
- Централизованный контроль безопасности
- Идеально для корпоративной среды, микросервисов
- Поддерживает MFA, LDAP, OAuth2, Google Login и др.
❌ Минусы
- Одна точка отказа (если IdP не работает — никто не войдёт)
- Нужно уметь правильно конфигурировать
- Требует понимания протоколов (OIDC/SAML)
OAuth 2.0 и OpenID Connect
OAuth 2.0 — это протокол авторизации, который позволяет приложению получить доступ к данным пользователя на другом сервисе, без передачи логина и пароля.
🎯 OAuth 2.0 — не протокол аутентификации, а делегированный доступ к ресурсам. Для входа пользователя поверх OAuth 2.0 используют OpenID Connect (OIDC). «OAuth2 — это протокол, который позволяет приложениям действовать от имени пользователя, не зная его пароля. Всё через токены, всё под контролем.»
Пример:
Ты логинишься на сайте через Google:
- Этот сайт не получает твой пароль
- Google показывает: “Это приложение хочет доступ к вашей почте”
- Ты нажимаешь “Разрешить”
- Приложение получает токен — и может читать твои письма (или что там запросили)
Кто участвует в процессе
- Resource Owner — пользователь (ты)
- Client — приложение, которое хочет доступ (например, Zoom, Notion)
- Authorization Server — кто выдает токен (например, Google)
- Resource Server — API, к которому клиент хочет доступ (например, Gmail API)
Основной поток для входа: OIDC Authorization Code Flow
Этапы:
- Клиент создаёт
state, а для public clients — также PKCEcode_verifier/code_challenge, сохраняет значения на время операции и перенаправляет пользователя на authorization endpoint:
https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=...&redirect_uri=...&scope=openid%20profile%20email&state=...&code_challenge=...&code_challenge_method=S256
- Пользователь логинится и разрешает доступ
- Google редиректит обратно с
code:
https://your-app.com/callback?code=abc123&state=...
- Клиент проверяет
stateи обменивает одноразовыйcodeвместе сcode_verifierна access token и, если разрешено, refresh token. - Клиент использует полученный access token. OAuth 2.0 не требует, чтобы этот токен был JWT:
Authorization: Bearer <token>
sequenceDiagram
title OAuth 2.0 в FastAPI (Authorization Code Flow)
actor Client as Клиент
participant FastAPI as FastAPI сервер
participant OAuth as OAuth 2.0 сервер
participant DB as База данных
Client->>+FastAPI: GET /login
FastAPI-->>-Client: Redirect на OAuth сервер<br>302 Location: https://oauth-provider.com/authorize?client_id=xyz&redirect_uri=...
Client->>+OAuth: GET /authorize?client_id=xyz&...
OAuth-->>-Client: Страница входа OAuth провайдера
Client->>+OAuth: POST /authorize (ввод логина/пароля)
OAuth-->>-Client: Redirect на redirect_uri<br>302 Location: https://your-app.com/callback?code=auth_code
Client->>+FastAPI: GET /callback?code=auth_code
FastAPI->>+OAuth: POST /token<br>code=auth_code&client_id=xyz&client_secret=secret
OAuth-->>-FastAPI: {"access_token": "token", "refresh_token": "refresh", ...}
FastAPI->>+OAuth: GET /userinfo<br>Authorization: Bearer token
OAuth-->>-FastAPI: {"sub": "123", "name": "User", "email": "..."}
FastAPI->>+DB: Сохранение/обновление данных пользователя
DB-->>-FastAPI: OK
Note over FastAPI: Создание сессии
FastAPI-->>-Client: Redirect на главную страницу<br>302 Location: / + Set-Cookie: session=...
Note over Client,FastAPI: Последующие запросы с сессионной cookie
Client->>+FastAPI: GET /secure-resource<br>Cookie: session=...
FastAPI-->>-Client: 200 OK + данные
Note over Client,FastAPI: Обновление OAuth токена (при необходимости)
FastAPI->>+OAuth: POST /token<br>grant_type=refresh_token&refresh_token=refresh&client_id=xyz&client_secret=secret
OAuth-->>-FastAPI: {"access_token": "new_token", "refresh_token": "new_refresh", ...}
Note over Client,FastAPI: Выход
Client->>+FastAPI: GET /logout
FastAPI-->>-Client: 200 OK<br>Set-Cookie: session=, expires=Thu, 01 Jan 1970...
Access и refresh token
Refresh token позволяет запросить новый access token без повторного участия resource owner. Он относится к OAuth 2.0, а не является особенностью JWT, и сам может быть непрозрачной строкой.
- Access Token — временный (часто 1 час), используется для запросов к API
- Refresh Token — долговечный, используется для получения нового access token без логина
OAuth ≠ Login
OAuth не занимается “кто ты”, он занимается “можно ли тебе это”.
Но если добавить OIDC (OpenID Connect) — появляется ID Token, и можно использовать OAuth2 как логин (SSO).
✅ Плюсы OAuth2
- Не требует передавать логины/пароли другим сервисам
- Можно ограничить доступ по scope (
read,write,profile) - Работает с большинством крупных API
- Основной протокол для “Войти через Google/Facebook/GitHub”
❌ Минусы
- Сложная конфигурация (client_id, redirect_uri, state)
Стандарты и рекомендации по безопасности
- RFC 9700: Best Current Practice for OAuth 2.0 Security
- RFC 6749: OAuth 2.0 Authorization Framework
- RFC 7636: Proof Key for Code Exchange (PKCE)
- OpenID Connect Core 1.0
- OWASP Session Management Cheat Sheet
- Есть много вариантов (flows): authorization code, client credentials, etc.
- Если сделать неправильно — легко открыть уязвимость
Keycloak (SSO / OpenID Connect)
Keycloak — это готовая система управления пользователями и авторизацией, которая реализует протоколы OIDC (OpenID Connect) и OAuth2.0. Она предоставляет SSO, MFA, вход через сторонние сервисы и централизованный контроль доступа.
Keycloak — это всё в одном: логин, токены, роли, пользователи, OAuth2, OIDC, MFA и SSO.
Как работает:
- Пользователь кликает “Войти” в одном из приложений
- Браузер редиректится на Keycloak
- Keycloak логинит пользователя (через форму, LDAP, Google и т.д.)
- Возвращает пользователя с
codeобратно в приложение - Приложение обменивает
codeнаaccess token,ID token,refresh token - Пользователь получает доступ к сервису
- В другом приложении пользователь уже авторизован (SSO)
Механика (технически):
- Keycloak — это Identity Provider (IdP)
- Работает через OIDC (добавляет аутентификацию к OAuth2)
- Выдаёт токены:
- Access token (доступ к API)
- ID token (информация о пользователе)
- Refresh token (обновление токенов)
- Поддерживает:
- JWT (RS256 по умолчанию)
- Google/GitHub/LDAP как сторонние IdP
- MFA (2FA), политики доступа, роли, группы
sequenceDiagram
title Keycloak Authentication в FastAPI
actor Client as Клиент
participant FastAPI as FastAPI сервер
participant Keycloak as Keycloak сервер
participant DB as База данных FastAPI
Note over Client,Keycloak: Вход через Keycloak
Client->>+FastAPI: GET /login
FastAPI-->>-Client: Redirect на Keycloak<br>302 Location: https://keycloak.com/realms/my-realm/protocol/openid-connect/auth?...
Client->>+Keycloak: GET /realms/my-realm/protocol/openid-connect/auth?...
Keycloak-->>-Client: Форма входа Keycloak
Client->>+Keycloak: POST /realms/my-realm/protocol/openid-connect/auth<br>(логин/пароль)
alt Успешная аутентификация
Keycloak-->>-Client: Redirect на callback<br>302 Location: https://your-app.com/auth/callback?code=...
Client->>+FastAPI: GET /auth/callback?code=...
FastAPI->>+Keycloak: POST /realms/my-realm/protocol/openid-connect/token<br>code=...&client_id=...&client_secret=...
Keycloak-->>-FastAPI: {"access_token": "eyJ...", "refresh_token": "eyJ...", "id_token": "eyJ..."}
FastAPI->>+Keycloak: GET /realms/my-realm/protocol/openid-connect/userinfo<br>Authorization: Bearer eyJ...
Keycloak-->>-FastAPI: {"sub": "uuid", "email": "user@example.com", "name": "User Name", "roles": ["user"]}
FastAPI->>+DB: Сохранение/обновление данных пользователя и ролей
DB-->>-FastAPI: OK
FastAPI-->>-Client: 302 Redirect на / + Set-Cookie: session=...
Note over Client,FastAPI: Использование сервиса с сессией
Client->>+FastAPI: GET /secure-resource<br>Cookie: session=...
alt Доступ разрешен
FastAPI-->>-Client: 200 OK + данные
else Доступ запрещен
FastAPI-->>Client: 403 Forbidden
end
else Неудачная аутентификация
Keycloak-->>Client: Ошибка входа
end
Note over Client,FastAPI: Проверка ролей
Client->>+FastAPI: GET /admin-resource<br>Cookie: session=...
note over FastAPI: Проверка роли "admin"<br>из токена Keycloak
alt Роль "admin" присутствует
FastAPI-->>-Client: 200 OK + данные
else Роль "admin" отсутствует
FastAPI-->>Client: 403 Forbidden
end
Note over Client,Keycloak: Выход из системы
Client->>+FastAPI: GET /logout
FastAPI->>+Keycloak: POST /realms/my-realm/protocol/openid-connect/logout<br>refresh_token=...
Keycloak-->>-FastAPI: OK
FastAPI-->>-Client: 302 Redirect на /<br>+ Set-Cookie: session=, expires=Thu, 01 Jan 1970...
✅ Плюсы:
- Централизованная аутентификация
- Полноценный SSO. Входит в одно — авторизован во всём
- Поддержка стандартов: OIDC, OAuth2, SAML
- Гибкость: UI логин, MFA, роли, политики, custom flows
- Масштабируемо и стабильно: Отлично подходит для production / enterprise
❌ Минусы:
- Нужно разобраться с realмами, клиентами, маппингами
- Требует сервера с Keycloak, либо Docker
- Единая точка отказа (IdP): Если Keycloak упал — никто не войдёт
- Сложно для маленьких проектов
Где применимо:
✅ Подходит:
- Микросервисная архитектура
- Корпоративные платформы
- Приложения с множеством клиентов и ролей
- SSO через Google, GitHub, LDAP ❌ Не подходит:
- Простейшие проекты без сложных пользователей
- MVP с одним входом
- Там, где нет инфраструктуры
Пример на FastAPI:
- Приложение регистрируется как "Client" в Keycloak
- Пользователь логинится → возвращается
code - FastAPI обменивает
codeна токены через HTTP-запрос:
import os
import httpx
async def exchange_code_for_token(code: str, code_verifier: str):
# До вызова нужно проверить state из callback против сохранённого значения.
async with httpx.AsyncClient(timeout=10) as client:
response = await client.post(
"https://keycloak.example/realms/myrealm/protocol/openid-connect/token",
data={
"client_id": "myapp",
"client_secret": os.environ["OIDC_CLIENT_SECRET"],
"grant_type": "authorization_code",
"code": code,
"code_verifier": code_verifier,
"redirect_uri": "https://myapp.example/callback",
},
)
response.raise_for_status()
return response.json()
Сравнение видов авторизации
Таблица — ориентир, а не универсальный рейтинг. Безопасность и масштабируемость зависят от модели угроз, реализации, хранения credentials/tokens, отзыва, ротации ключей и инфраструктуры.
| Критерий | 🟤 **Basic Auth** | 🔵 **Session Auth** | 🟢 **JWT Auth** | 🟣 **OAuth 2.0** | 🟡 **Keycloak (OIDC/SSO)** |
| **Тип** | Простая передача пароля | Сессионная (stateful) | Token-based; может требовать серверного состояния | Протокол авторизации | IdP-платформа (реализация OAuth2/OIDC) |
| **Хранение состояния** | Stateless (каждый запрос) | На сервере (память, БД) | Зависит от отзыва, сессий и проверки permissions | Authorization server хранит clients, grants, keys и часто refresh/revocation state | IdP хранит пользователей, clients, keys и сессии |
| **Где хранятся данные** | В заголовке `Authorization` | В cookie (sessionid) | В JWT-токене (`payload`) | В токенах (access/refresh) | В токенах, управляется IdP |
| **Механизм хранения** | Base64: `user:pass` | `Set-Cookie: sessionid` | `Authorization: Bearer |
`Authorization: Bearer |
`Authorization: Bearer |
| **Можно выйти (logout)?** | Схема не стандартизует logout | ✅ Да | ⚠️ Частично (через blacklist) | Зависит от token type и поддержки revocation endpoint | ✅ Да (через endpoint или IdP logout) |
| **Простота реализации** | 🟢 Очень простая | 🟡 Простая | 🟡 Средняя | 🔴 Сложная (flows, scopes) | 🔴 Сложная (Keycloak, настройки, UI) |
| **Хорошо для API?** | Да, для простых доверенных клиентов | Да, особенно для browser/BFF | Да, если JWT соответствует контракту | Да, для делегированного доступа | Да, для централизованного identity/access management |
| **Поддержка SSO** | ❌ Нет | ❌ Нет | ⚠️ Частично ( самописный IdP) | Через OIDC, не OAuth сам по себе | ✅ Полноценный SSO |
| **Поддержка ролей/scopes** | Реализует приложение | ✅ На сервере | В claims и/или на сервере; данные могут устаревать | ✅ В `scopes` | ✅ Через UI, токены, RBAC |
| **Безопасность** | Требует HTTPS и безопасного хранения credentials | Требует Secure/HttpOnly cookie, CSRF-защиты и rotation | Зависит от подписи, claims, хранения, срока жизни и отзыва | Зависит от корректного flow, PKCE/state и политики токенов | Зависит от конфигурации, обновлений, MFA и политик |
| **Масштабируемость** | Зависит от проверки credentials и backend | Требует масштабируемого session store или sticky sessions | Локальная проверка удобна, но отзыв/permissions могут требовать state | Зависит от authorization server и resource servers | Зависит от кластера IdP и внешних хранилищ |
| **Подходит для микросервисов** | Возможно, но редко удобно | Возможно через gateway/BFF или общий session store | Да, при корректной проверке issuer/audience/permissions | Да, для делегированного доступа между участниками | Да, когда нужен централизованный IdP |
| **Когда использовать** | Временные внутренние API | Классические формы/сайты | Современные SPA/API | Вход через Google/доступ к API | Корпоративные, SSO, продакшн-платформы |

Это удобно, когда нужно: