HookRegistryService
Package: @nauth-toolkit/core/internal
Type: Service (Internal)
Central registry for managing authentication lifecycle hooks. Handles hook registration and execution with proper error handling and logging.
- NestJS
- Express
- Fastify
import { HookRegistryService } from '@nauth-toolkit/nestjs';
import { NAuth } from '@nauth-toolkit/core';
const nauth = await NAuth.create({ config, dataSource, adapter });
const hookRegistry = nauth.hookRegistry;
import { NAuth } from '@nauth-toolkit/core';
const nauth = await NAuth.create({ config, dataSource, adapter });
const hookRegistry = nauth.hookRegistry;
Overview
Provides centralized hook management for authentication lifecycle events. Hooks are executed in registration order.
Auto-injected by framework adapters. Manual instantiation not recommended.
Methods
executeAccountLocked()
Internal method. Executes all registered account locked hooks. Called automatically by AuthServiceInternalHelpers. Errors are logged but don't block lockout.
async executeAccountLocked(metadata: AccountLockedMetadata): Promise<void>
executeAccountStatusChanged()
Internal method. Executes all registered account status changed hooks. Called automatically by UserService. Errors are logged but don't block the operation.
async executeAccountStatusChanged(metadata: AccountStatusChangedMetadata): Promise<void>
executeAdaptiveMFARiskDetected()
Internal method. Executes all registered adaptive MFA risk detected hooks. Called automatically by AdaptiveMFADecisionService. Errors are logged but don't block authentication.
async executeAdaptiveMFARiskDetected(metadata: AdaptiveMFARiskDetectedMetadata): Promise<void>
executeEmailChanged()
Internal method. Executes all registered email changed hooks. Called automatically by UserService. Errors are logged but don't block the operation.
async executeEmailChanged(metadata: EmailChangedMetadata): Promise<void>
executeMFADeviceRemoved()
Internal method. Executes all registered MFA device removed hooks. Called automatically by UserService and MFAService. Errors are logged but don't block the operation.
async executeMFADeviceRemoved(metadata: MFADeviceRemovedMetadata): Promise<void>
executeMFAFirstEnabled()
Internal method. Executes all registered MFA first enabled hooks. Called automatically by BaseMFAProviderService. Errors are logged but don't block MFA enrollment.
async executeMFAFirstEnabled(metadata: MFAFirstEnabledMetadata): Promise<void>
executeMFAMethodAdded()
Internal method. Executes all registered MFA method added hooks. Called automatically by BaseMFAProviderService. Errors are logged but don't block MFA enrollment.
async executeMFAMethodAdded(metadata: MFAMethodAddedMetadata): Promise<void>
executeOnboardingCompleted()
Internal method. Executes all registered onboarding completed hooks. Called automatically by AuthService, EmailVerificationService, and PhoneVerificationService. Errors are logged but don't block the flow.
async executeOnboardingCompleted(user: IUser, metadata: OnboardingCompletedMetadata): Promise<void>
Parameters
user- User entitymetadata- Completion metadata (verification method, source, timestamp)
executePasswordChanged()
Internal method. Executes all registered password changed hooks. Called automatically by AuthServiceInternalHelpers. Errors are logged but don't block the operation.
async executePasswordChanged(metadata: PasswordChangedMetadata): Promise<void>
executePostSignup()
Internal method. Executes all registered post-signup hooks in order. Called automatically by AuthService. Errors are logged but don't block signup.
async executePostSignup(user: IUser, metadata?: SignupMetadata): Promise<void>
Parameters
user- Created user entitymetadata- Optional signup metadata
executePreSignup()
Internal method. Executes all registered pre-signup hooks in order. Called automatically by AuthService.
async executePreSignup(
data: PreSignupHookData,
signupType: 'password' | 'social',
provider?: string,
adminSignup?: boolean,
): Promise<void>
Parameters
data-SignupDTO,AdminSignupDTO, orOAuthUserProfiledepending on signup typesignupType- Type of signup ('password' or 'social')provider- Social provider name (e.g., 'google', 'apple', 'facebook') - only for social signupsadminSignup- Whether this is an admin-initiated signup
Errors
| Code | When | Details |
|---|---|---|
PRESIGNUP_FAILED | Hook throws exception | { message: string } |
Throws NAuthException with code PRESIGNUP_FAILED if any hook throws an error.
executeSessionsRevoked()
Internal method. Executes all registered sessions revoked hooks. Called automatically by SessionService. Errors are logged but don't block the operation.
async executeSessionsRevoked(metadata: SessionsRevokedMetadata): Promise<void>
executeUserProfileUpdated()
Internal method. Executes all registered user profile updated hooks in order. Called automatically by AuthService, EmailVerificationService, and PhoneVerificationService. Errors are logged but don't block updates.
async executeUserProfileUpdated(metadata: UserProfileUpdatedMetadata): Promise<void>
Parameters
metadata-UserProfileUpdatedMetadatacontaining updated user and change details
registerAccountLocked()
Register an account locked hook. Hooks execute when an account is locked due to failed login attempts. Non-blocking - errors are logged.
registerAccountLocked(provider: IAccountLockedHook): void
Parameters
provider-IAccountLockedHook
Example
- NestJS
- Express
- Fastify
@Injectable()
@AccountLockedHook()
export class LockoutNotificationHook implements IAccountLockedHook {
async execute(metadata) {
await this.alertService.notifyAdmin('account_locked', { userId: metadata.user.sub });
}
}
class LockoutNotificationHook implements IAccountLockedHook {
async execute(metadata) {
await alertService.notifyAdmin('account_locked', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerAccountLocked(new LockoutNotificationHook());
class LockoutNotificationHook implements IAccountLockedHook {
async execute(metadata) {
await alertService.notifyAdmin('account_locked', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerAccountLocked(new LockoutNotificationHook());
registerAccountStatusChanged()
Register an account status changed hook. Hooks execute when an account is enabled or disabled by an admin. Non-blocking - errors are logged.
registerAccountStatusChanged(provider: IAccountStatusChangedHook): void
Parameters
provider-IAccountStatusChangedHook
Example
- NestJS
- Express
- Fastify
@Injectable()
@AccountStatusChangedHook()
export class AccountStatusHook implements IAccountStatusChangedHook {
async execute(metadata) {
await this.auditService.log('account_status_changed', {
userId: metadata.user.sub,
status: metadata.newStatus,
});
}
}
class AccountStatusHook implements IAccountStatusChangedHook {
async execute(metadata) {
await auditService.log('account_status_changed', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerAccountStatusChanged(new AccountStatusHook());
class AccountStatusHook implements IAccountStatusChangedHook {
async execute(metadata) {
await auditService.log('account_status_changed', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerAccountStatusChanged(new AccountStatusHook());
registerAdaptiveMFARiskDetected()
Register an adaptive MFA risk detected hook. Hooks execute when a risk factor is detected during authentication. Non-blocking - errors are logged.
registerAdaptiveMFARiskDetected(provider: IAdaptiveMFARiskDetectedHook): void
Parameters
provider-IAdaptiveMFARiskDetectedHook
Example
- NestJS
- Express
- Fastify
@Injectable()
@AdaptiveMFARiskDetectedHook()
export class RiskLoggingHook implements IAdaptiveMFARiskDetectedHook {
async execute(metadata) {
await this.securityService.logRiskEvent(metadata.user.sub, metadata.riskFactors);
}
}
class RiskLoggingHook implements IAdaptiveMFARiskDetectedHook {
async execute(metadata) {
await securityService.logRiskEvent(metadata.user.sub, metadata.riskFactors);
}
}
nauth.hookRegistry.registerAdaptiveMFARiskDetected(new RiskLoggingHook());
class RiskLoggingHook implements IAdaptiveMFARiskDetectedHook {
async execute(metadata) {
await securityService.logRiskEvent(metadata.user.sub, metadata.riskFactors);
}
}
nauth.hookRegistry.registerAdaptiveMFARiskDetected(new RiskLoggingHook());
registerEmailChanged()
Register an email changed hook. Hooks execute after a user's email address is changed. Non-blocking - errors are logged.
registerEmailChanged(provider: IEmailChangedHook): void
Parameters
provider-IEmailChangedHook
Example
- NestJS
- Express
- Fastify
@Injectable()
@EmailChangedHook()
export class EmailChangedHook implements IEmailChangedHook {
async execute(metadata) {
await this.crmService.updateEmail(metadata.user.sub, metadata.newEmail);
}
}
class EmailSyncHook implements IEmailChangedHook {
async execute(metadata) {
await crmService.updateEmail(metadata.user.sub, metadata.newEmail);
}
}
nauth.hookRegistry.registerEmailChanged(new EmailSyncHook());
class EmailSyncHook implements IEmailChangedHook {
async execute(metadata) {
await crmService.updateEmail(metadata.user.sub, metadata.newEmail);
}
}
nauth.hookRegistry.registerEmailChanged(new EmailSyncHook());
registerMFADeviceRemoved()
Register an MFA device removed hook. Hooks execute after an MFA device is removed from a user account. Non-blocking - errors are logged.
registerMFADeviceRemoved(provider: IMFADeviceRemovedHook): void
Parameters
provider-IMFADeviceRemovedHook
Example
- NestJS
- Express
- Fastify
@Injectable()
@MFADeviceRemovedHook()
export class SecurityAlertHook implements IMFADeviceRemovedHook {
async execute(metadata) {
await this.notifyService.sendSecurityAlert(metadata.user.email, 'mfa_device_removed');
}
}
class SecurityAlertHook implements IMFADeviceRemovedHook {
async execute(metadata) {
await notifyService.sendSecurityAlert(metadata.user.email, 'mfa_device_removed');
}
}
nauth.hookRegistry.registerMFADeviceRemoved(new SecurityAlertHook());
class SecurityAlertHook implements IMFADeviceRemovedHook {
async execute(metadata) {
await notifyService.sendSecurityAlert(metadata.user.email, 'mfa_device_removed');
}
}
nauth.hookRegistry.registerMFADeviceRemoved(new SecurityAlertHook());
registerMFAFirstEnabled()
Register an MFA first enabled hook. Hooks execute when a user enables MFA for the first time on their account. Non-blocking - errors are logged.
registerMFAFirstEnabled(provider: IMFAFirstEnabledHook): void
Parameters
provider-IMFAFirstEnabledHook
Example
- NestJS
- Express
- Fastify
@Injectable()
@MFAFirstEnabledHook()
export class MFAEnrollmentHook implements IMFAFirstEnabledHook {
async execute(metadata) {
await this.analyticsService.track('mfa_first_enabled', { userId: metadata.user.sub });
}
}
class MFAEnrollmentHook implements IMFAFirstEnabledHook {
async execute(metadata) {
await analyticsService.track('mfa_first_enabled', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerMFAFirstEnabled(new MFAEnrollmentHook());
class MFAEnrollmentHook implements IMFAFirstEnabledHook {
async execute(metadata) {
await analyticsService.track('mfa_first_enabled', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerMFAFirstEnabled(new MFAEnrollmentHook());
registerMFAMethodAdded()
Register an MFA method added hook. Hooks execute when an additional MFA method is added to an account that already has MFA enabled. Non-blocking - errors are logged.
registerMFAMethodAdded(provider: IMFAMethodAddedHook): void
Parameters
provider-IMFAMethodAddedHook
Example
- NestJS
- Express
- Fastify
@Injectable()
@MFAMethodAddedHook()
export class MFAMethodHook implements IMFAMethodAddedHook {
async execute(metadata) {
await this.analyticsService.track('mfa_method_added', {
userId: metadata.user.sub,
method: metadata.method,
});
}
}
class MFAMethodHook implements IMFAMethodAddedHook {
async execute(metadata) {
await analyticsService.track('mfa_method_added', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerMFAMethodAdded(new MFAMethodHook());
class MFAMethodHook implements IMFAMethodAddedHook {
async execute(metadata) {
await analyticsService.track('mfa_method_added', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerMFAMethodAdded(new MFAMethodHook());
registerOnboardingCompleted()
Register an onboarding completed hook. Hooks execute when a user completes onboarding (email/phone verification). Non-blocking - errors are logged.
registerOnboardingCompleted(provider: IOnboardingCompletedHook): void
Parameters
provider-IOnboardingCompletedHook
Example
- NestJS
- Express
- Fastify
@Injectable()
@OnboardingCompletedHook()
export class OnboardingHook implements IOnboardingCompletedHook {
async execute(user, metadata) {
await this.analyticsService.track('onboarding_completed', {
userId: user.sub,
method: metadata.verificationMethod,
});
}
}
class OnboardingHook implements IOnboardingCompletedHook {
async execute(user, metadata) {
await analyticsService.track('onboarding_completed', { userId: user.sub });
}
}
nauth.hookRegistry.registerOnboardingCompleted(new OnboardingHook());
class OnboardingHook implements IOnboardingCompletedHook {
async execute(user, metadata) {
await analyticsService.track('onboarding_completed', { userId: user.sub });
}
}
nauth.hookRegistry.registerOnboardingCompleted(new OnboardingHook());
registerPasswordChanged()
Register a password changed hook. Hooks execute after a user's password is changed. Non-blocking - errors are logged.
registerPasswordChanged(provider: IPasswordChangedHook): void
Parameters
provider-IPasswordChangedHook
Example
- NestJS
- Express
- Fastify
@Injectable()
@PasswordChangedHook()
export class PasswordAuditHook implements IPasswordChangedHook {
async execute(metadata) {
await this.auditService.log('password_changed', { userId: metadata.user.sub });
}
}
class PasswordAuditHook implements IPasswordChangedHook {
async execute(metadata) {
await auditService.log('password_changed', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerPasswordChanged(new PasswordAuditHook());
class PasswordAuditHook implements IPasswordChangedHook {
async execute(metadata) {
await auditService.log('password_changed', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerPasswordChanged(new PasswordAuditHook());
registerPostSignup()
Register a post-signup hook provider. Hooks execute after successful user creation. Non-blocking - errors are logged.
registerPostSignup(provider: IPostSignupHookProvider): void
Parameters
provider-IPostSignupHookProvider
Example
- NestJS
- Express
- Fastify
// Use decorators - automatic registration
@Injectable()
@PostSignupHook()
export class WelcomeEmailHook implements IPostSignupHookProvider {
constructor(private emailService: EmailService) {}
async execute(user, metadata) {
// For social signups, include profile picture
if (metadata?.signupType === 'social' && metadata.profilePicture) {
await this.emailService.sendWelcome({
email: user.email,
profilePicture: metadata.profilePicture,
});
} else {
await this.emailService.sendWelcome(user.email);
}
}
}
class WelcomeEmailHook implements IPostSignupHookProvider {
constructor(private emailService: EmailService) {}
async execute(user, metadata) {
await this.emailService.sendWelcome(user.email);
}
}
nauth.hookRegistry.registerPostSignup(new WelcomeEmailHook(emailService));
class WelcomeEmailHook implements IPostSignupHookProvider {
constructor(private emailService: EmailService) {}
async execute(user, metadata) {
await this.emailService.sendWelcome(user.email);
}
}
nauth.hookRegistry.registerPostSignup(new WelcomeEmailHook(emailService));
registerPreSignup()
Register a pre-signup hook provider. Hooks execute before user creation and can block signups.
registerPreSignup(provider: IPreSignupHookProvider): void
Parameters
provider-IPreSignupHookProvider
Example
- NestJS
- Express
- Fastify
import { Injectable } from '@nestjs/common';
import { PreSignupHook, IPreSignupHookProvider, PreSignupHookData } from '@nauth-toolkit/nestjs';
// Use decorators - automatic registration
@Injectable()
@PreSignupHook()
export class MyHook implements IPreSignupHookProvider {
async execute(
data: PreSignupHookData,
signupType: 'password' | 'social',
provider?: string,
adminSignup?: boolean,
): Promise<void> {
// Validation logic
}
}
class MyHook implements IPreSignupHookProvider {
async execute(userData, signupMethod, providerId, adminSignup) {
// Validation logic
}
}
nauth.hookRegistry.registerPreSignup(new MyHook());
class MyHook implements IPreSignupHookProvider {
async execute(userData, signupMethod, providerId, adminSignup) {
// Validation logic
}
}
nauth.hookRegistry.registerPreSignup(new MyHook());
registerSessionsRevoked()
Register a sessions revoked hook. Hooks execute when user sessions are revoked (logout, global sign-out, admin action). Non-blocking - errors are logged.
registerSessionsRevoked(provider: ISessionsRevokedHook): void
Parameters
provider-ISessionsRevokedHook
Example
- NestJS
- Express
- Fastify
@Injectable()
@SessionsRevokedHook()
export class SessionAuditHook implements ISessionsRevokedHook {
async execute(metadata) {
await this.auditService.log('sessions_revoked', {
userId: metadata.user.sub,
sessionCount: metadata.sessionCount,
});
}
}
class SessionAuditHook implements ISessionsRevokedHook {
async execute(metadata) {
await auditService.log('sessions_revoked', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerSessionsRevoked(new SessionAuditHook());
class SessionAuditHook implements ISessionsRevokedHook {
async execute(metadata) {
await auditService.log('sessions_revoked', { userId: metadata.user.sub });
}
}
nauth.hookRegistry.registerSessionsRevoked(new SessionAuditHook());
registerUserProfileUpdated()
Register a user profile updated hook provider. Hooks execute after profile attribute changes. Non-blocking - errors are logged.
registerUserProfileUpdated(provider: IUserProfileUpdatedHook): void
Parameters
provider-IUserProfileUpdatedHook
Example
- NestJS
- Express
- Fastify
@Injectable()
@UserProfileUpdatedHook()
export class CrmSyncHook implements IUserProfileUpdatedHook {
async execute(metadata: UserProfileUpdatedMetadata) {
const emailChange = metadata.changedFields.find((f) => f.fieldName === 'email');
if (emailChange) {
await this.crmService.updateContact(metadata.user.sub, {
email: emailChange.newValue,
});
}
}
}
class CrmSyncHook implements IUserProfileUpdatedHook {
async execute(metadata) {
const emailChange = metadata.changedFields.find((f) => f.fieldName === 'email');
if (emailChange) {
await crmService.updateContact(metadata.user.sub, emailChange.newValue);
}
}
}
nauth.hookRegistry.registerUserProfileUpdated(new CrmSyncHook());
class CrmSyncHook implements IUserProfileUpdatedHook {
async execute(metadata) {
const emailChange = metadata.changedFields.find((f) => f.fieldName === 'email');
if (emailChange) {
await crmService.updateContact(metadata.user.sub, emailChange.newValue);
}
}
}
nauth.hookRegistry.registerUserProfileUpdated(new CrmSyncHook());
Execution Order
Hooks execute in registration order:
// First hook registered
hookRegistry.registerPreSignup(domainValidation);
// Second hook registered
hookRegistry.registerPreSignup(inviteCodeCheck);
// Execution order during signup:
// 1. domainValidation.execute()
// 2. inviteCodeCheck.execute()
Stopping Execution:
For pre-signup hooks, first hook to throw NAuthException stops execution and blocks signup:
// Hook 1: Throws error
domainValidation.execute(); // Throws PRESIGNUP_FAILED
// Hook 2: Never executes
inviteCodeCheck.execute(); // Skipped
All other hooks are non-blocking:
All hooks execute regardless of errors. Errors are caught and logged:
// Hook 1: Throws error
welcomeEmail.execute(); // Throws error - logged, continues
// Hook 2: Still executes
analytics.execute(); // Executes normally
Error Handling
Pre-Signup Hooks:
- Errors with code
PRESIGNUP_FAILEDare re-thrown as-is - Other errors are wrapped in
PRESIGNUP_FAILEDwith original message - First error stops execution and blocks signup
All Other Hooks:
- All errors are caught and logged
- Execution continues to next hook
- The triggering operation is never blocked
Related APIs
- IPreSignupHookProvider - Pre-signup hook interface
- IPostSignupHookProvider - Post-signup hook interface
- IUserProfileUpdatedHook - User profile updated hook interface
- Lifecycle Hooks Guide - Complete usage guide