Головна Наші публікації
Автентифікація NestJS: access tokens, refresh tokens і відкликання сесій

Автентифікація 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-автентифікації NestJS із короткоживучими access tokens, rotation refresh tokens, серверними сесіями, виявленням replay та відкликанням доступу.

Автентифікація 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.
Вашій автентифікації NestJS потрібен security review?
Розкажіть про clients, identity flows, token storage, session model, compliance constraints та incident scenarios. Ми визначимо конкретні прогалини й спроєктуємо пропорційну систему автентифікації та revocation.
Публікації

Наші дослідження

Дослідження та розробка рішень на основі штучного інтелекту для оптимізації бізнес-процесів і підвищення ефективності прийняття рішень.

Аналіз моделей машинного навчання для прогнозної аналітики у фінансовій сфері, електронній комерції та SaaS-платформах.

Дослідження технологій обробки природної мови та комп'ютерного зору для посилення автоматизації, персоналізації та підтримки клієнтів.

Ми використовуємо файли cookie для забезпечення безпеки та належної роботи нашого сайту. За вашою згодою ми також використовуємо необов'язкові файли cookie для аналітики та рекламних цілей. Ви можете прийняти або відхилити використання необов'язкових файлів cookie. Ви можете змінити свої налаштування в будь-який час. Докладніше — у нашій Політиці щодо файлів cookie.