@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-emitterRegister 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.