Материал

Виды авторизации

сказать

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

План:

Терминология

Для начала освежим в памяти базовые термины, ими мы будем оперировать дальше Иллюстрация к материалу «Виды авторизации»

Идентификация (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
**Идемпотентность** - свойство операции, которое заключается в том, что повторное выполнение этой операции с теми же входными данными будет иметь тот же эффект, как и однократное выполнение. ### Пример запроса: ```http GET /index.html HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive Upgrade-Insecure-Requests: 1 ``` ### Пример ответа: ```http HTTP/1.1 200 OK Date: Mon, 27 Nov 2023 12:00:00 GMT Server: Apache/2.4.29 (Unix) Last-Modified: Wed, 01 Nov 2023 10:00:00 GMT ETag: "a1f-5c001a1f364d8" Accept-Ranges: bytes Content-Type: text/html Example Page

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 секунд”. Иллюстрация к материалу «Виды авторизации»

Как работает:

  1. Пользователь вводит логин и пароль
  2. Эти данные кодируются в base64: username:password Base64 не шифрует, а просто кодирует → нужен HTTPS
  3. В каждом запросе отправляется заголовок:
Authorization: Basic YWRtaW46c2VjcmV0
  1. Сервер декодирует заголовок и проверяет логин/пароль вручную или через 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-панель
  • Не лезть в код приложения вообще

Как работает:

  1. Пользователь заходит на защищённый URL
  2. Браузер показывает окно ввода логина/пароля
  3. Nginx проверяет их с помощью **файла ****.htpasswd**
  4. Только после этого отдаёт доступ
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

  1. Пользователь логинится — отправляет логин/пароль → POST /login с {"username": "...", "password": "..."}
  2. Сервер проверяет их и создаёт сессию → Сохраняем в памяти или базе связь сессии с учеткой
  3. Сервер присваивает сессии уникальный ID (обычно — случайная строка) → Генерируем session_id
  4. Этот ID отправляется пользователю в cookie → например,
HTTP/1.1 200 OK
Set-Cookie: sessionid=abc123; HttpOnly; Path=/; Secure
  1. При каждом следующем запросе браузер автоматически отправляет cookie → например
GET /profile HTTP/1.1
Host: example.com
Cookie: sessionid=abc123
  1. Сервер по 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 для хранения сессий. Ключевые моменты:

  1. Сессионный ID генерируется как UUID и хранится в Redis с ограниченным временем жизни
  2. Cookie устанавливается с HttpOnly, Secure и подходящим SameSite. Для изменяющих состояние запросов всё равно нужна CSRF-защита, соответствующая архитектуре приложения
  3. Реализованы основные эндпоинты: вход, выход и получение информации о текущем пользователе
  4. При выходе сессия удаляется из 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)

  1. Пользователь отправляет логин и парольPOST /login с {"username": "...", "password": "..."}
  2. Сервер проверяет учётные данные → Сравнивает логин и пароль с логин-паролем из базы/хранилища.
  3. Сервер создаёт JWT access token → С помощью функции из пункта 2. По сути генерирует специальную строку и не хранит ее, просто возвращает как результат метода
  4. Токен возвращается пользователю → В теле ответа или HttpOnly cookie
  5. Клиент сохраняет или удерживает токен → Вариант хранения выбирают по модели угроз. localStorage доступен JavaScript и особенно чувствителен к XSS. HttpOnly cookie недоступна JavaScript, но автоматически отправляется браузером и требует защиты от CSRF. Cookie не становится безопасной автоматически: важны Secure, HttpOnly, подходящий SameSite и защита приложения от XSS.

*LocalStorage — это хранилище в браузере, где можно сохранять данные в формате **ключ:значение*

  1. Дальнейшие запросы идут с токеном → В заголовке: Authorization: Bearer <token>
  2. Сервер проверяет подпись и exp токена → Если всё ок — пользователь авторизован
  3. Если 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 состоит из пяти частей. Каждая часть:

  1. Header — метаданные: тип токена и алгоритм подписи
  2. Payload — данные (claims): кто вы, срок действия, роли и т.д.
  3. 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 ❌ Опционально
**Алгоритмы подписи: HS256 vs RS256** - **HS256** — симметричная подпись: все стороны, которые проверяют токен, знают общий секрет и технически могут подписывать новые токены. - **RS256** — асимметричная подпись: частный ключ используется для подписи, публичный — для проверки. Это удобно, когда несколько resource servers проверяют токены, не получая ключ подписи. Выбор алгоритма зависит от архитектуры и политики безопасности. > Ты можешь начать с **HS256**, а в полном проекте — перейти на **RS256 или интеграцию с Keycloak**, где это будет работать автоматически. ### ⚠️ **Ошибки, которые часто совершают:** - ❌ Не ставят exp (и получают “вечные” токены) - ❌ Добавляют в токен **пароль** или чувствительные данные - ❌ Используют слишком короткий или слабый `SECRET_KEY` - ❌ Отправляют токен без Bearer схемы в заголовке - ❌ Не верифицируют подпись на сервере ## Refresh token flow > Refresh token используется для получения нового `access token` без повторной аутентификации пользователя. Его основная схема работы:
  1. При авторизации клиент получает два токена:
  • Access token (короткоживущий, например 15-30 минут)
  • Refresh token (долгоживущий, например 7-30 дней)
  1. Когда access token истекает:
  • Клиент отправляет refresh token на специальный endpoint (например, POST /refresh)
  • Сервер проверяет валидность refresh токена
  • При успешной проверке сервер генерирует новый access token и возвращает его клиенту
  • Опционально: генерирует новый refresh token (rotation strategy)
  1. Схема запроса обновления:
POST /refresh
Content-Type: application/json

{
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ..."
}
  1. Ответ:
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC...", // Опционально
  "token_type": "Bearer",
  "expires_in": 900
}

Особенности refresh токенов:

  • Структура: Могут быть JWT или непрозрачными случайными строками с достаточной энтропией.
  • Хранение на сервере: Зависит от дизайна authorization server. Для rotation, обнаружения повторного использования и отзыва обычно требуется серверное состояние; секретные токены в хранилище желательно защищать так же тщательно, как другие credentials.
  • Хранение на клиенте: Для браузерного клиента часто используют HttpOnly, Secure cookie с подходящим SameSite, но конкретная схема зависит от архитектуры и модели угроз.
  • Срок жизни: Значительно дольше access токена, но имеет конечный срок

Рекомендации по реализации:

  • Создавайте уникальный refresh token для каждого устройства пользователя
  • Для public clients используйте rotation refresh token или sender-constrained refresh token и обнаруживайте повторное использование старого токена.

Отзыв JWT (revoke)

Одним из недостатков JWT является сложность отзыва токенов, так как они по умолчанию валидны до истечения срока. Существует несколько подходов к решению этой проблемы:

  1. 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}")
  1. Контроль через Refresh токены
  • Храните только refresh токены в базе данных
  • При логауте - удаляйте refresh токен из базы
  • Держите короткий срок жизни access токенов
  1. Выпуск с версией/идентификатором пользовательской сессии
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 ⚠️ Зависит от реализации ❌ Небезопасно Требует шифрования
**Рекомендуемый подход:** - **Access token**: `HttpOnly` cookie или in-memory storage - **Refresh token**: Только `HttpOnly`, `Secure`, `SameSite=strict cookie` ### Дополнительные меры безопасности: 1. **Anti-CSRF токены** - Генерируйте уникальный токен для каждой сессии - Проверяйте его при запросах, влияющих на состояние 2. **Fingerprinting устройства** - Привязывайте refresh токен к характеристикам устройства - Проверяйте соответствие при обновлении токена ## SSO (Single-sign on) > **SSO (Single Sign-On)** — это механизм, при котором пользователь логинится один раз, чтобы получить доступ ко всем связанным системам, без повторной аутентификации.

🎯 Сам SSO — это поведение, а не технология. А реализуется он через Keycloak, Okta, Azure AD и т.п.

  1. User (пользователь) — хочет войти, знает свой логин и пароль
  2. Service Provider (SP) — сайт, в который он заходит (например, GitLab)
  3. Identity Provider (IdP) — сервис, который подтверждает личность (например, Keycloak, Google, Okta)

Как это выглядит для пользователя:

  1. Заходишь на app1.company.com → логинишься
  2. Переходишь на app2.company.com → уже залогинен
  3. Переходишь в dashboard.partner.com → тоже залогинен → без ввода пароля

Как это работает под капотом

  1. Пользователь заходит на Service A
  2. Его редиректят на IdP
  3. Он логинится один раз
  4. IdP возвращает токен или сессию
  5. Пользователь получает доступ к Service A
  6. При переходе на Service B, тот снова обращается к IdP → уже есть сессия → вход без пароля

SSO работает через протоколы:

  • SAML — старый, XML, часто в enterprise
  • OpenID Connect (OIDC) — надстройка над OAuth2, современный стандарт
  • Иногда — Kerberos (в Windows-доменных сетях)

Пример:

Допустим, у тебя есть три приложения:

  • billing.domain.com
  • docs.domain.com
  • admin.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 показывает: “Это приложение хочет доступ к вашей почте”
  • Ты нажимаешь “Разрешить”
  • Приложение получает токен — и может читать твои письма (или что там запросили)

Кто участвует в процессе

  1. Resource Owner — пользователь (ты)
  2. Client — приложение, которое хочет доступ (например, Zoom, Notion)
  3. Authorization Server — кто выдает токен (например, Google)
  4. Resource Server — API, к которому клиент хочет доступ (например, Gmail API)

Основной поток для входа: OIDC Authorization Code Flow

Этапы:

  1. Клиент создаёт state, а для public clients — также PKCE code_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
  1. Пользователь логинится и разрешает доступ
  2. Google редиректит обратно с code:
https://your-app.com/callback?code=abc123&state=...
  1. Клиент проверяет state и обменивает одноразовый code вместе с code_verifier на access token и, если разрешено, refresh token.
  2. Клиент использует полученный 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)

Стандарты и рекомендации по безопасности

Keycloak (SSO / OpenID Connect)

Keycloak — это готовая система управления пользователями и авторизацией, которая реализует протоколы OIDC (OpenID Connect) и OAuth2.0. Она предоставляет SSO, MFA, вход через сторонние сервисы и централизованный контроль доступа.

Keycloak — это всё в одном: логин, токены, роли, пользователи, OAuth2, OIDC, MFA и SSO.

Как работает:

  1. Пользователь кликает “Войти” в одном из приложений
  2. Браузер редиректится на Keycloak
  3. Keycloak логинит пользователя (через форму, LDAP, Google и т.д.)
  4. Возвращает пользователя с code обратно в приложение
  5. Приложение обменивает code на access token, ID token, refresh token
  6. Пользователь получает доступ к сервису
  7. В другом приложении пользователь уже авторизован (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, продакшн-платформы