
Аутентификация NestJS: access tokens, refresh tokens и отзыв сессий
- NestJS authentication
- access token
- refresh token
- token rotation
- session revocation
- JWT security
- logout all devices

Аутентификация NestJS: access tokens, refresh tokens и отзыв сессий
Production-архитектура аутентификации должна отвечать не только на вопрос, как подписать JWT. Она обязана ограничивать последствия кражи token, различать устройства, корректно обрабатывать параллельные refresh requests, отзывать доступ после logout или смены пароля и предоставлять доказательства при replay. Долгоживущий bearer JWT не выполняет эти требования самостоятельно: для его использования достаточно владеть token, который обычно остается действительным до expiration.
Практичный типовой подход разделяет ответственность. Короткоживущий access token авторизует API requests без обращения к database при каждом вызове. Refresh token с высокой entropy продолжает конкретную серверную сессию и заменяется после каждого использования. Database становится источником истины для долгосрочного доступа, а lifetime access token ограничивает задержку до полного применения отзыва. Наши услуги разработки NestJS адаптируют эту модель к threat model продукта, типам клиентов, compliance и операционным ограничениям.
Определите границы доверия для login и tokens
При login валидируйте DTO, ограничивайте частоту попыток, ищите account без раскрытия факта его существования, проверяйте пароль с помощью password-hashing function и применяйте account, MFA и risk policies. Создавайте сессию только после успешного прохождения всех обязательных проверок. Храните случайный session ID, user ID, family ID refresh token, digest token, время создания и завершения, последнее использование, состояние отзыва и минимально необходимый device context. Никогда не храните raw refresh token.
Подписывайте access token с явно заданным algorithm и управляемым key. Оставляйте claims минимальными: subject, session ID, token ID, issuer, audience, issued-at и expiration, а также только стабильные authorization data, которые могут безопасно оставаться неактуальными в течение короткого lifetime. Каждый NestJS guard должен проверять signature и ограничивать принимаемые algorithm, issuer, audience и time claims. Декодирование JWT не является проверкой, а аутентификация не заменяет авторизацию на границе route или resource.
Моделируйте refresh tokens как отзываемые сессии
Используйте opaque refresh token, созданный криптографически безопасным random source. Передавайте secret клиенту один раз, а в database храните только keyed digest. Таблица сессий должна поддерживать absolute expiration, idle expiration, явный отзыв и обход token family. Device label может помочь пользователю распознать сессию, однако user-agent и IP — лишь сигналы, а не надежная identity; смена IP нормальна и не должна приводить к случайной блокировке.
Для browser-приложений предпочитайте Secure, HttpOnly cookie, чтобы обычный JavaScript не мог прочитать refresh credential. Выбирайте SameSite согласно реальному cross-site flow, по возможности ограничивайте Path, требуйте HTTPS и реализуйте CSRF protection, когда cookies аутентифицируют state-changing request. Не размещайте tokens в URL. Native и другим клиентам нужны platform-protected credential storage и threat model, учитывающая особенности public client.
CREATE TABLE auth_session (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
family_id UUID NOT NULL,
refresh_token_digest CHAR(64) NOT NULL UNIQUE,
replaced_by_session_id UUID NULL,
created_at TIMESTAMPTZ NOT NULL,
last_used_at TIMESTAMPTZ NOT NULL,
idle_expires_at TIMESTAMPTZ NOT NULL,
absolute_expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ NULL,
revoke_reason VARCHAR(64) NULL,
CONSTRAINT auth_session_replacement_fk
FOREIGN KEY (replaced_by_session_id) REFERENCES auth_session(id)
);
CREATE INDEX auth_session_user_active_idx
ON auth_session (user_id, revoked_at);
CREATE INDEX auth_session_family_idx
ON auth_session (family_id);Выполняйте rotation атомарно и обнаруживайте replay
Каждый успешный refresh должен делать предоставленный token недействительным и возвращать новый refresh token, связанный с той же family. Выполняйте поиск, валидацию, invalidation и insert в одной database transaction с row lock или эквивалентным compare-and-set rule. Иначе два одновременных requests могут принять один token и создать конкурирующих successors. Client code должен сериализовать refresh calls, а не запускать отдельный request для каждого неудачного API call.
Если уже замененный token появляется снова, предполагайте возможную кражу. Server не может определить, кто обратился первым — attacker или legitimate client, — поэтому отзывайте активную token family, отклоняйте request и требуйте новой аутентификации. Записывайте security event без credentials. Небольшой явно спроектированный retry grace может понадобиться для ненадежной сети, но свободное принятие старых tokens разрушает replay detection.
async rotate(presentedToken: string): Promise<TokenPair> {
const digest = this.tokenDigest(presentedToken);
const outcome = await this.dataSource.transaction(async (manager) => {
const current = await manager.findOne(AuthSession, {
where: { refreshTokenDigest: digest },
lock: { mode: "pessimistic_write" },
});
if (!current || this.isExpired(current)) {
throw new UnauthorizedException();
}
if (current.replacedBySessionId) {
await this.revokeFamily(manager, current.familyId, "refresh_reuse");
return { kind: "reused" } as const;
}
if (current.revokedAt) {
throw new UnauthorizedException();
}
const rawNext = randomBytes(32).toString("base64url");
const next = manager.create(AuthSession, {
id: randomUUID(), userId: current.userId,
familyId: current.familyId,
refreshTokenDigest: this.tokenDigest(rawNext),
absoluteExpiresAt: current.absoluteExpiresAt,
idleExpiresAt: this.nextIdleExpiry(),
});
current.replacedBySessionId = next.id;
current.lastUsedAt = new Date();
await manager.save([current, next]);
return {
kind: "rotated" as const,
pair: await this.issuePair(current.userId, next.id, rawNext),
};
});
// Throw after commit so replay-triggered family revocation is preserved.
if (outcome.kind === "reused") throw new UnauthorizedException();
return outcome.pair;
}Сделайте logout и отзыв на всех устройствах явными
Logout на этом устройстве отзывает текущую сессию или family и очищает refresh cookie с теми же Path, Domain, Secure и SameSite attributes, которые использовались при установке. Endpoint должен быть idempotent: повторный logout не должен восстанавливать или раскрывать сессию. «Logout на всех устройствах» отзывает все активные сессии пользователя, желательно одной операцией, которая также фиксирует инициатора и причину.
Password reset, подтвержденный захват account, деактивация account, высокорисковые credential changes и administrator action также могут требовать отзыва на всех устройствах. Действующие access JWT могут работать до expiration. Если бизнесу нужен немедленный запрет, добавьте проверку session status или user security version на чувствительных границах либо поддерживайте короткоживущий revocation cache. Это ускоряет реакцию, но сознательно отменяет полностью stateless validation; задокументируйте последствия для availability и latency.
Тестируйте отказы, а не только happy path
Тестируйте invalid signatures, неправильные algorithms, issuers и audiences, expired и not-yet-valid tokens, malformed claims, revoked sessions, idle и absolute expiry, replay замененного refresh token, два одновременных refresh requests, повторный logout, очистку cookie, отклонение CSRF и отзыв на всех устройствах. Убедитесь, что logs, traces, analytics и exception messages никогда не содержат passwords или raw tokens.
Отслеживайте login failures, успешные и неудачные refresh, reuse detection, revocations, создание сессий по account и необычные изменения geography или device, не превращая слабые сигналы в автоматическое доказательство compromise. Осторожно ограничивайте частоту login и refresh endpoints, оповещайте о значимых patterns, меняйте signing keys в течение задокументированного overlap period и отрабатывайте emergency key compromise. Аутентификация — это управляемая security system, а не controller, который достаточно создать один раз.
Чек-лист production-аутентификации
- Access tokens имеют короткий обоснованный lifetime, а guards проверяют algorithm, signature, issuer, audience и time claims.
- Refresh tokens являются secrets с высокой entropy, защищены при передаче и на клиенте и хранятся на server только как digests.
- Rotation атомарен, сохраняет absolute lifetime и отзывает token family при обнаружении повторного использования.
- Logout текущего устройства, logout всех устройств, password reset и поведение при account compromise явно определены и протестированы.
- Cookie attributes, CORS и CSRF controls соответствуют реальному browser deployment, а не development defaults.
- Key rotation, security telemetry, incident response и logging без credentials имеют задокументированных owners.
Должны ли refresh tokens тоже быть JWT?
Какой lifetime должен быть у access и refresh tokens?
Устраняет ли HttpOnly cookie риски CSRF и XSS?
Может ли logout немедленно сделать access JWT недействительным?
Наши исследования
Исследование и разработка решений на основе искусственного интеллекта для оптимизации бизнес-процессов и повышения эффективности принятия решений.
Анализ моделей машинного обучения для прогнозной аналитики в сфере финансов, электронной коммерции и SaaS-платформ.
Исследование технологий обработки естественного языка и компьютерного зрения для усиления автоматизации, персонализации и поддержки клиентов.


