NestboltNestbolt

@nestbolt/authentication

Events

Complete reference for all authentication lifecycle events emitted by @nestbolt/authentication, with payload types and listener examples.

The authentication module emits events at key points in the authentication lifecycle. Events are emitted via the @nestjs/event-emitter package when it is installed. If the package is not installed, no events are emitted and the module operates normally.

Setup

Install the event emitter package:

npm install @nestjs/event-emitter

Register the EventEmitterModule in your application module before the AuthenticationModule:

import { Module } from "@nestjs/common";
import { EventEmitterModule } from "@nestjs/event-emitter";
import { AuthenticationModule, Feature } from "@nestbolt/authentication";

@Module({
  imports: [
    EventEmitterModule.forRoot(),
    AuthenticationModule.forRoot({
      features: [Feature.REGISTRATION],
      // ... other options
    }),
  ],
})
export class AppModule {}

Listening to Events

Use the @OnEvent() decorator from @nestjs/event-emitter to subscribe to events. Create an injectable service with decorated methods:

import { Injectable } from "@nestjs/common";
import { OnEvent } from "@nestjs/event-emitter";
import { AUTH_EVENTS, UserEvent } from "@nestbolt/authentication";

@Injectable()
export class AuthEventListener {
  @OnEvent(AUTH_EVENTS.LOGIN)
  handleLogin(payload: UserEvent) {
    console.log(`User ${payload.user.email} logged in`);
  }

  @OnEvent(AUTH_EVENTS.REGISTERED)
  handleRegistration(payload: UserEvent) {
    console.log(`New user registered: ${payload.user.email}`);
    // Send welcome email, create default settings, etc.
  }
}

Register the listener as a provider in your module:

@Module({
  providers: [AuthEventListener],
})
export class AppModule {}

Event Constants

All event names are available via the AUTH_EVENTS constant object:

import { AUTH_EVENTS } from "@nestbolt/authentication";
const AUTH_EVENTS = {
  LOGIN: "auth.login",
  LOGOUT: "auth.logout",
  REGISTERED: "auth.registered",
  LOCKOUT: "auth.lockout",
  PASSWORD_RESET: "auth.password-reset",
  PASSWORD_UPDATED: "auth.password-updated",
  EMAIL_VERIFIED: "auth.email-verified",
  TWO_FACTOR_ENABLED: "auth.two-factor-enabled",
  TWO_FACTOR_DISABLED: "auth.two-factor-disabled",
  TWO_FACTOR_CONFIRMED: "auth.two-factor-confirmed",
  TWO_FACTOR_CHALLENGED: "auth.two-factor-challenged",
  TWO_FACTOR_FAILED: "auth.two-factor-failed",
  VALID_TWO_FACTOR_CODE: "auth.valid-two-factor-code",
  RECOVERY_CODE_REPLACED: "auth.recovery-code-replaced",
  RECOVERY_CODES_GENERATED: "auth.recovery-codes-generated",
} as const;

Payload Types

Three payload types are used across all events:

import {
  UserEvent,
  LockoutEvent,
  RecoveryCodeReplacedEvent,
} from "@nestbolt/authentication";

UserEvent

The most common payload. Contains the user object.

interface UserEvent {
  user: AuthUser;
}

Where AuthUser is:

interface AuthUser {
  id: string;
  name: string;
  email: string;
  password: string;
  emailVerifiedAt: Date | null;
  twoFactorSecret: string | null;
  twoFactorRecoveryCodes: string | null;
  twoFactorConfirmedAt: Date | null;
  passwordConfirmedAt: Date | null;
}

LockoutEvent

Emitted when a login rate limit is reached. Contains the raw request object (not the user, since authentication failed).

interface LockoutEvent {
  request: Record<string, unknown>;
}

RecoveryCodeReplacedEvent

Emitted when a recovery code is used during a two-factor challenge. Contains the user and the code that was consumed.

interface RecoveryCodeReplacedEvent extends UserEvent {
  user: AuthUser;
  code: string;
}

Events Reference

auth.login

Constant: AUTH_EVENTS.LOGIN

Payload: UserEvent

Emitted when: A user successfully logs in (credentials validated and tokens generated). Not emitted when a two-factor challenge is required -- in that case, auth.two-factor-challenged is emitted instead, and auth.login is not emitted until the challenge is completed.

@OnEvent(AUTH_EVENTS.LOGIN)
handleLogin(payload: UserEvent) {
  // Log the login, update last_login_at, track IP, etc.
  console.log(`User ${payload.user.id} logged in`);
}

auth.logout

Constant: AUTH_EVENTS.LOGOUT

Payload: UserEvent

Emitted when: A user calls POST /logout.

@OnEvent(AUTH_EVENTS.LOGOUT)
handleLogout(payload: UserEvent) {
  // Clean up sessions, log the event, etc.
  console.log(`User ${payload.user.id} logged out`);
}

auth.registered

Constant: AUTH_EVENTS.REGISTERED

Payload: UserEvent

Emitted when: A new user is successfully registered via POST /register.

@OnEvent(AUTH_EVENTS.REGISTERED)
handleRegistration(payload: UserEvent) {
  // Send welcome email
  // Create default user settings
  // Notify admins
  // Track analytics
  console.log(`New user: ${payload.user.email}`);
}

auth.lockout

Constant: AUTH_EVENTS.LOCKOUT

Payload: LockoutEvent

Emitted when: A login attempt is blocked by the LoginThrottleGuard because the rate limit has been exceeded.

@OnEvent(AUTH_EVENTS.LOCKOUT)
handleLockout(payload: LockoutEvent) {
  // Log the lockout for security monitoring
  // Send alert to security team
  // Block the IP address
  const ip = (payload.request as any).ip;
  console.log(`Login lockout from IP: ${ip}`);
}

Note that the request object is the raw NestJS request. Cast it to access properties like ip, body, or headers.


auth.password-reset

Constant: AUTH_EVENTS.PASSWORD_RESET

Payload: UserEvent

Emitted when: A user's password is successfully reset via the POST /reset-password endpoint (the forgot-password flow).

@OnEvent(AUTH_EVENTS.PASSWORD_RESET)
handlePasswordReset(payload: UserEvent) {
  // Send notification email confirming the password change
  // Invalidate other sessions
  console.log(`Password reset for: ${payload.user.email}`);
}

auth.password-updated

Constant: AUTH_EVENTS.PASSWORD_UPDATED

Payload: UserEvent

Emitted when: A user changes their password via PUT /user/password (the authenticated password change flow).

@OnEvent(AUTH_EVENTS.PASSWORD_UPDATED)
handlePasswordUpdated(payload: UserEvent) {
  // Send notification email
  // Invalidate other sessions/tokens
  console.log(`Password updated for: ${payload.user.email}`);
}

auth.email-verified

Constant: AUTH_EVENTS.EMAIL_VERIFIED

Payload: UserEvent

Emitted when: A user's email address is successfully verified via GET /email/verify/:id/:hash. Only emitted the first time the email is verified (not on subsequent visits to the verification link).

@OnEvent(AUTH_EVENTS.EMAIL_VERIFIED)
handleEmailVerified(payload: UserEvent) {
  // Unlock features that require verified email
  // Update user privileges
  console.log(`Email verified: ${payload.user.email}`);
}

auth.two-factor-enabled

Constant: AUTH_EVENTS.TWO_FACTOR_ENABLED

Payload: UserEvent

Emitted when: Two-factor authentication is enabled for a user via POST /user/two-factor-authentication. This event fires when the TOTP secret is generated and stored, before the optional confirmation step.

@OnEvent(AUTH_EVENTS.TWO_FACTOR_ENABLED)
handleTwoFactorEnabled(payload: UserEvent) {
  // Log the security event
  // Send notification email
  console.log(`2FA enabled for: ${payload.user.email}`);
}

auth.two-factor-disabled

Constant: AUTH_EVENTS.TWO_FACTOR_DISABLED

Payload: UserEvent

Emitted when: Two-factor authentication is disabled for a user via DELETE /user/two-factor-authentication. The TOTP secret, recovery codes, and confirmation timestamp are cleared.

@OnEvent(AUTH_EVENTS.TWO_FACTOR_DISABLED)
handleTwoFactorDisabled(payload: UserEvent) {
  // Log the security event
  // Send warning notification email
  console.log(`2FA disabled for: ${payload.user.email}`);
}

auth.two-factor-confirmed

Constant: AUTH_EVENTS.TWO_FACTOR_CONFIRMED

Payload: UserEvent

Emitted when: A user confirms their 2FA setup by providing a valid TOTP code via POST /user/confirmed-two-factor-authentication. This sets the twoFactorConfirmedAt timestamp.

@OnEvent(AUTH_EVENTS.TWO_FACTOR_CONFIRMED)
handleTwoFactorConfirmed(payload: UserEvent) {
  // 2FA is now fully active for this user
  console.log(`2FA confirmed for: ${payload.user.email}`);
}

auth.two-factor-challenged

Constant: AUTH_EVENTS.TWO_FACTOR_CHALLENGED

Payload: UserEvent

Emitted when: During login, a user with 2FA enabled is presented with a two-factor challenge (instead of receiving tokens directly). This happens at the POST /login endpoint when the user's credentials are valid but 2FA is required.

@OnEvent(AUTH_EVENTS.TWO_FACTOR_CHALLENGED)
handleTwoFactorChallenged(payload: UserEvent) {
  // Log that a 2FA challenge was initiated
  console.log(`2FA challenge for: ${payload.user.email}`);
}

auth.two-factor-failed

Constant: AUTH_EVENTS.TWO_FACTOR_FAILED

Payload: UserEvent

Emitted when: A two-factor challenge fails -- either an invalid TOTP code or an invalid recovery code was provided at POST /two-factor-challenge.

@OnEvent(AUTH_EVENTS.TWO_FACTOR_FAILED)
handleTwoFactorFailed(payload: UserEvent) {
  // Log failed 2FA attempt for security monitoring
  // Alert on repeated failures
  console.log(`Failed 2FA attempt for: ${payload.user.email}`);
}

auth.valid-two-factor-code

Constant: AUTH_EVENTS.VALID_TWO_FACTOR_CODE

Payload: UserEvent

Emitted when: A two-factor challenge is completed successfully at POST /two-factor-challenge -- either with a valid TOTP code or a valid recovery code. This is emitted before the access/refresh tokens are returned.

@OnEvent(AUTH_EVENTS.VALID_TWO_FACTOR_CODE)
handleValidTwoFactorCode(payload: UserEvent) {
  // 2FA login successful
  console.log(`2FA validated for: ${payload.user.email}`);
}

auth.recovery-code-replaced

Constant: AUTH_EVENTS.RECOVERY_CODE_REPLACED

Payload: RecoveryCodeReplacedEvent

Emitted when: A recovery code is used during a two-factor challenge. The used code is consumed and replaced with a new one. The payload includes the consumed code.

@OnEvent(AUTH_EVENTS.RECOVERY_CODE_REPLACED)
handleRecoveryCodeReplaced(payload: RecoveryCodeReplacedEvent) {
  // Warn the user that a recovery code was used
  // Alert on recovery code usage (potential account compromise)
  console.log(
    `Recovery code used by ${payload.user.email}: ${payload.code}`,
  );
}

auth.recovery-codes-generated

Constant: AUTH_EVENTS.RECOVERY_CODES_GENERATED

Payload: UserEvent

Emitted when: Recovery codes are regenerated via POST /user/two-factor-recovery-codes. All existing codes are replaced with 8 new ones.

@OnEvent(AUTH_EVENTS.RECOVERY_CODES_GENERATED)
handleRecoveryCodesGenerated(payload: UserEvent) {
  // Log the regeneration for audit purposes
  console.log(`Recovery codes regenerated for: ${payload.user.email}`);
}

Complete Listener Example

Here is a complete listener service that handles all authentication events:

import { Injectable, Logger } from "@nestjs/common";
import { OnEvent } from "@nestjs/event-emitter";
import {
  AUTH_EVENTS,
  UserEvent,
  LockoutEvent,
  RecoveryCodeReplacedEvent,
} from "@nestbolt/authentication";

@Injectable()
export class AuthEventListener {
  private readonly logger = new Logger(AuthEventListener.name);

  @OnEvent(AUTH_EVENTS.LOGIN)
  handleLogin({ user }: UserEvent) {
    this.logger.log(`Login: ${user.email}`);
  }

  @OnEvent(AUTH_EVENTS.LOGOUT)
  handleLogout({ user }: UserEvent) {
    this.logger.log(`Logout: ${user.email}`);
  }

  @OnEvent(AUTH_EVENTS.REGISTERED)
  handleRegistered({ user }: UserEvent) {
    this.logger.log(`Registration: ${user.email}`);
    // TODO: send welcome email
  }

  @OnEvent(AUTH_EVENTS.LOCKOUT)
  handleLockout({ request }: LockoutEvent) {
    this.logger.warn(`Lockout from IP: ${(request as any).ip}`);
  }

  @OnEvent(AUTH_EVENTS.PASSWORD_RESET)
  handlePasswordReset({ user }: UserEvent) {
    this.logger.log(`Password reset: ${user.email}`);
    // TODO: send confirmation email
  }

  @OnEvent(AUTH_EVENTS.PASSWORD_UPDATED)
  handlePasswordUpdated({ user }: UserEvent) {
    this.logger.log(`Password updated: ${user.email}`);
    // TODO: send notification email
  }

  @OnEvent(AUTH_EVENTS.EMAIL_VERIFIED)
  handleEmailVerified({ user }: UserEvent) {
    this.logger.log(`Email verified: ${user.email}`);
  }

  @OnEvent(AUTH_EVENTS.TWO_FACTOR_ENABLED)
  handleTwoFactorEnabled({ user }: UserEvent) {
    this.logger.log(`2FA enabled: ${user.email}`);
  }

  @OnEvent(AUTH_EVENTS.TWO_FACTOR_DISABLED)
  handleTwoFactorDisabled({ user }: UserEvent) {
    this.logger.warn(`2FA disabled: ${user.email}`);
    // TODO: send security alert email
  }

  @OnEvent(AUTH_EVENTS.TWO_FACTOR_CONFIRMED)
  handleTwoFactorConfirmed({ user }: UserEvent) {
    this.logger.log(`2FA confirmed: ${user.email}`);
  }

  @OnEvent(AUTH_EVENTS.TWO_FACTOR_CHALLENGED)
  handleTwoFactorChallenged({ user }: UserEvent) {
    this.logger.log(`2FA challenge initiated: ${user.email}`);
  }

  @OnEvent(AUTH_EVENTS.TWO_FACTOR_FAILED)
  handleTwoFactorFailed({ user }: UserEvent) {
    this.logger.warn(`2FA failed: ${user.email}`);
  }

  @OnEvent(AUTH_EVENTS.VALID_TWO_FACTOR_CODE)
  handleValidTwoFactorCode({ user }: UserEvent) {
    this.logger.log(`2FA validated: ${user.email}`);
  }

  @OnEvent(AUTH_EVENTS.RECOVERY_CODE_REPLACED)
  handleRecoveryCodeReplaced({ user, code }: RecoveryCodeReplacedEvent) {
    this.logger.warn(`Recovery code used: ${user.email}`);
    // TODO: send security alert email
  }

  @OnEvent(AUTH_EVENTS.RECOVERY_CODES_GENERATED)
  handleRecoveryCodesGenerated({ user }: UserEvent) {
    this.logger.log(`Recovery codes regenerated: ${user.email}`);
  }
}

Using String Literals

If you prefer string literals over the AUTH_EVENTS constant, you can use the event names directly:

@OnEvent("auth.login")
handleLogin(payload: UserEvent) {
  // ...
}

However, using AUTH_EVENTS.LOGIN is recommended for type safety and to avoid typos.

Async Event Handlers

Event handlers can be asynchronous. The @nestjs/event-emitter package supports both synchronous and asynchronous handlers:

@OnEvent(AUTH_EVENTS.REGISTERED)
async handleRegistration({ user }: UserEvent) {
  await this.emailService.sendWelcomeEmail(user.email, user.name);
  await this.analyticsService.trackSignup(user.id);
}

Note that by default, @nestjs/event-emitter does not wait for async handlers to complete before continuing execution. If you need to handle errors in async listeners, wrap them in try/catch blocks.

Next Steps

  • Configuration -- configure rate limits, token expiry, and other options.
  • API Routes -- see which routes emit which events.