Коротко:
- OAuth 2.0 - протокол делегированной авторизации с несколькими флоу; JWT - формат токена с подписью и полезной нагрузкой. Их тестируют по-разному, но в связке.
- Главное, что пропускают: проверка поведения при истёкшем токене, корректность обновления сессии и обработка подмены payload.
- Большинство edge-кейсов не видно в happy path - они всплывают при граничных значениях времени, повторных запросах и нестандартных сценариях выхода.
- Для ручного разбора структуры удобен jwt.io; для автоматизации - Postman pre-request scripts или pytest с библиотекой PyJWT.
- Refresh token требует отдельного набора проверок: одноразовость, ротация, поведение после отзыва.
Зачем это вообще тестировать отдельно
OAuth 2.0 и JWT встречаются почти в каждом современном продукте - от мобильных приложений до корпоративных порталов. При этом большинство команд проверяют только happy path: пользователь залогинился, токен пришёл, запрос прошёл. Этого мало.
Ошибки в механизме авторизации стоят дорого: пользователь видит чужие данные, сессия не завершается после выхода, скомпрометированный токен продолжает работать. Всё это не экзотика - а типичные баги, которые обнаруживаются уже после релиза, потому что их не искали целенаправленно.
Эта статья - про конкретные сценарии. Что именно проверять, где обычно ломается и как выстроить покрытие так, чтобы не полагаться на удачу.
Как устроен JWT: что важно знать для тестирования
JSON Web Token состоит из трёх частей, разделённых точками: header, payload и signature. Header содержит алгоритм подписи (например, HS256 или RS256). Payload - произвольные поля: идентификатор пользователя, роли, время выдачи (iat), время истечения (exp). Signature подтверждает, что токен не изменяли.
Важный нюанс: payload закодирован в base64, но не зашифрован. Любой, у кого есть токен, может прочитать содержимое - просто вставив его в jwt.io. Это значит, что в payload нельзя класть секреты: пароли, платёжные данные, внутренние ключи.
Для тестировщика это открывает возможности: можно посмотреть, что реально выдаёт сервер, какие роли указаны, какое время истечения установлено - и сравнить с ожидаемым поведением.
Флоу OAuth 2.0: что и когда нужно проверять
OAuth 2.0 описывает несколько сценариев получения доступа (grants). Чаще всего встречаются три:
- Authorization Code - пользователь логинится через браузер, получает code, который обменивается на токены. Используется в веб-приложениях.
- Client Credentials - сервис-к-сервису, без участия пользователя. Нет ни code, ни refresh token.
- PKCE (Proof Key for Code Exchange) - расширение Authorization Code для мобильных и SPA-приложений.
Каждый флоу имеет свои граничные случаи. Authorization Code предполагает одноразовый code - его нельзя использовать дважды. PKCE добавляет code_verifier и code_challenge, которые тоже нужно проверять.
Спецификация OAuth 2.0 описана в RFC 6749, а PKCE - в RFC 7636. Это полезные источники, когда нужно понять, как система должна себя вести по стандарту.
Проверка структуры и содержимого токена
Первый шаг - убедиться, что токен вообще корректен по структуре. Невалидный JWT (лишние символы, неверное количество сегментов) должен отклоняться с 401, а не вызывать 500.
Дальше - содержимое payload. Что именно проверять:
exp- есть ли поле и разумное ли значение. Токен безexpникогда не истекает - это баг безопасности.iat- время выдачи. Помогает понять, не выдан ли токен в прошлом.aud- аудитория. Токен, выданный для одного сервиса, не должен приниматься другим.sub- идентификатор субъекта. Убедитесь, что это идентификатор нужного пользователя, а не соседнего.- Кастомные claims: роли, разрешения, tenant_id - всё, что добавляет ваша система.
Удобный способ посмотреть payload вручную - скопировать токен в jwt.io. Для автотестов используйте PyJWT (Python) или jsonwebtoken (Node.js): они позволяют декодировать и верифицировать содержимое программно.
Пример: Представим SaaS-продукт с несколькими тарифами. В payload хранится поле plan: "free". Если сервер не перепроверяет права на своей стороне и доверяет только токену, пользователь может вручную изменить payload (подделав подпись) и получить доступ к premium-функциям. Проверяйте, что backend валидирует подпись, а не просто декодирует содержимое.
Истечение сессии: граничные кейсы по времени
Поле exp задаёт временную метку в формате Unix timestamp. Токен считается истёкшим, когда текущее время сервера превышает это значение. Казалось бы, просто - но здесь скрыто несколько неочевидных ситуаций.
Что проверять:
- Запрос с токеном, у которого
expпрошёл 1 секунду назад. Сервер должен вернуть 401, а не 200. - Запрос прямо на границе истечения (clock skew). Если у клиента и сервера разные часы, возможны ложные отказы или, наоборот, принятие истёкшего токена. RFC 7519 допускает небольшой leeway (обычно не более 5 минут), но это решение на стороне сервера.
- Токен с
expв далёком прошлом (1970-й год или вчерашний день). Должен отклоняться. - Токен без поля
expвообще. Идеальный ответ - 401 с понятным сообщением об ошибке.
Для ручного тестирования удобно вручную генерировать токены с нужными значениями exp через jwt.io (с известным секретом на тестовом стенде) или через Postman pre-request script.
Pre-request script в Postman для генерации истёкшего токена:
const jwt = require('jsonwebtoken');
const secret = 'test-secret';
const token = jwt.sign(
{ sub: '123', exp: Math.floor(Date.now() / 1000) - 60 },
secret
);
pm.environment.set('expired_token', token);Postman поддерживает require('jsonwebtoken') через встроенные библиотеки. Готовый токен подставляется в заголовок Authorization автоматически.
Тестирование refresh token
Refresh token - долгоживущий артефакт, который позволяет получить новый access token без повторного логина. Именно здесь сосредоточено больше всего потенциально критичных ошибок.
| Сценарий | Ожидаемое поведение | Частая ошибка |
|---|---|---|
| Использование валидного refresh token | Новый access token + новый refresh token (при ротации) | Старый refresh token остаётся активным |
| Повторное использование уже применённого refresh token | 401 + инвалидация всей сессии | Сервер выдаёт новый токен без проверки |
| Refresh token после явного выхода пользователя | 401 | Токен остаётся действующим |
| Истёкший refresh token | 401, пользователь перенаправляется на логин | 500 или зависание запроса |
| Refresh token от другого пользователя | 401 | Принимается и выдаёт чужой access token |
Концепция refresh token rotation описана в документации Auth0: при каждом обновлении старый refresh token инвалидируется, выдаётся новый. Повторное использование старого должно сигнализировать о потенциальной компрометации и блокировать всю сессию.
Edge-кейсы авторизации: что чаще всего пропускают
Стандартный набор проверок покрывает очевидные пути. Edge-кейсы - это ситуации, которые в обычном сценарии не встречаются, но случаются у реальных пользователей или при атаке.
Подмена алгоритма (algorithm confusion)
В поле alg заголовка JWT можно указать none. Некоторые библиотеки принимают такой токен без проверки подписи. Это классическая атака, описанная в PortSwigger Web Security Academy. Проверьте: если отправить токен с alg: none и пустой подписью, сервер должен вернуть 401.
Схожий вариант - смена алгоритма с RS256 (асимметричный) на HS256 (симметричный). Если сервер принимает оба, можно попробовать подписать токен публичным ключом как секретом для HS256.
Параллельные сессии и инвалидация
Пользователь сменил пароль - все активные сессии должны завершиться. Администратор заблокировал аккаунт - запросы с действующим токеном должны получать 401. Проверяйте именно это: не только что новый токен не выдаётся, но и что старые перестают работать немедленно.
Scope и aud
Токен с правами на чтение не должен давать права на запись. Токен, выданный для сервиса A, не должен приниматься сервисом B. Это проверяется просто: берёте токен из одного контекста и используете его в другом. Ответ должен быть 403 (недостаточно прав) или 401 (неверная аудитория).
Отзыв токена (token revocation)
JWT по природе своей stateless: сервер не хранит список выданных токенов. Поэтому отзыв - нетривиальная задача. Если в вашей системе есть logout или принудительная инвалидация, проверьте: токен, добавленный в blacklist, должен отклоняться, даже если его exp ещё не наступил. Механизм описан в RFC 7009.
Конкурентные запросы на обновление
Два параллельных запроса с одним refresh token - что вернёт сервер? Если оба проходят и возвращают разные access token, это гонка состояний. Такой сценарий реален для мобильных приложений, которые делают несколько параллельных API-запросов при запуске.
Важно: Не тестируйте edge-кейсы безопасности на продакшн-среде. Используйте стейджинг или локальный стенд с изолированной базой данных и отдельными ключами подписи.
Инструменты: что и для чего
| Инструмент | Для чего подходит | Когда использовать |
|---|---|---|
| jwt.io | Декодирование и ручная проверка структуры | Быстрый разбор токена, ручные проверки payload |
| Postman + pre-request scripts | Автоподстановка токенов, тестирование refresh-флоу | Ручное и полуавтоматизированное тестирование API |
| PyJWT / jsonwebtoken | Генерация и верификация токенов в автотестах | pytest, Jest, CI-пайплайн |
| Burp Suite / OWASP ZAP | Перехват и модификация токенов | Проверка уязвимостей (alg confusion, подмена claims) |
| Newman | Запуск Postman-коллекций в CI | Автоматизация регрессии по авторизации |
Для большинства проверок достаточно Postman и jwt.io. Burp Suite нужен, если вы целенаправленно ищете уязвимости, а не просто проверяете корректность поведения.
Как выстроить автотесты для авторизационного флоу
Автотесты для OAuth 2.0 обычно разбивают на несколько уровней.
Unit-уровень - проверка самой логики валидации токена на стороне бэкенда: правильно ли код отклоняет истёкшие подписи, токены с неверным aud, токены без exp. Это быстрые тесты без сети.
Integration-уровень - реальные HTTP-запросы к авторизационному серверу. Получили токен, использовали, получили ожидаемый ответ. Здесь же - тест refresh-флоу: получить пару токенов, дождаться (или сдвинуть) истечения access token, обновить через refresh, убедиться, что новый работает.
E2E-уровень - полный пользовательский сценарий: логин через OAuth-провайдер, использование приложения, выход, проверка, что после выхода старые токены не принимаются.
Пример на pytest + PyJWT (integration):
import jwt
import requests
def test_expired_token_returns_401():
secret = 'test-secret'
expired = jwt.encode(
{'sub': '42', 'exp': 1}, # exp в далёком прошлом
secret,
algorithm='HS256'
)
response = requests.get(
'https://api.example.com/protected',
headers={'Authorization': f'Bearer {expired}'}
)
assert response.status_code == 401Главное в автотестах - не проверять только статус-код. Проверяйте тело ответа: есть ли понятное сообщение об ошибке, правильный ли error в теле (например, token_expired или invalid_token). Это важно для клиентских приложений, которые разбирают ошибки.
Типичные ошибки при тестировании
Проверять только happy path. Токен выдан - работает. Это закрывает 20% возможных проблем. Основная масса багов живёт в граничных состояниях: истечение, отзыв, параллельные запросы.
Не проверять содержимое токена. QA убедился, что запрос прошёл с кодом 200, но не посмотрел, что именно вернул сервер в payload. В итоге пользователь с ролью viewer получает токен с ролью admin - и это никто не замечает до инцидента.
Игнорировать поведение после logout. Пользователь нажал «Выйти». Если при этом refresh token не инвалидируется на сервере, его можно использовать повторно. Проверяйте это явно: после выхода попробуйте обновить сессию с тем же refresh token.
Не тестировать clock skew. На стейджинге часы сервера и клиента синхронизированы. В продакшне - нет. Если сервер не учитывает небольшое расхождение, пользователи с неточными часами будут получать 401 без видимой причины.
Пропускать проверку aud. В микросервисной архитектуре токен от сервиса A не должен принимать сервис B. Это проверяется за пять минут, но часто упускается.
Чеклист для QA
- Токен содержит поле
expс разумным значением. - Истёкший токен возвращает 401, а не 200 или 500.
- Токен без подписи или с невалидной подписью отклоняется.
- Токен с
alg: noneотклоняется. - Payload содержит корректные роли и идентификатор пользователя.
- Refresh token работает один раз (при включённой ротации).
- Повторное использование уже применённого refresh token возвращает 401.
- После logout refresh token инвалидируется на сервере.
- Смена пароля завершает все активные сессии.
- Токен с неверным
audотклоняется целевым сервисом. - Параллельные запросы на обновление не создают гонку состояний.
- Сообщения об ошибках информативны и не раскрывают внутренние детали реализации.