NestboltNestbolt

@nestbolt/authentication

Features

Detailed explanation of each Feature enum value in @nestbolt/authentication, including what routes, services, and action providers each feature enables.

The Feature enum controls which parts of the authentication system are active. Pass an array of Feature values to the features option when configuring the module. Only the routes and services for enabled features are registered (with forRoot()) or accessible (with forRootAsync()).

import { Feature } from "@nestbolt/authentication";

Feature.REGISTRATION

Value: "registration"

Enables user registration via the POST /register endpoint.

Routes Enabled

MethodRouteDescription
POST/registerCreate a new user account

Services Registered

  • RegistrationService -- orchestrates user creation and token generation.

Required Action Provider

This feature requires you to register a CreatesNewUsers implementation using the CREATES_NEW_USERS injection token. This action is responsible for validating the registration data, hashing the password, and persisting the new user.

import { Injectable, ConflictException } from "@nestjs/common";
import * as bcrypt from "bcrypt";
import { CreatesNewUsers, AuthUser } from "@nestbolt/authentication";

@Injectable()
export class CreateNewUser implements CreatesNewUsers {
  async create(data: Record<string, any>): Promise<AuthUser> {
    // Validate uniqueness, hash password, create user
    const hashedPassword = await bcrypt.hash(data.password, 12);
    // ... persist to your database and return the created user
  }
}

Register it in your module:

import { CREATES_NEW_USERS } from "@nestbolt/authentication";

@Module({
  providers: [
    { provide: CREATES_NEW_USERS, useClass: CreateNewUser },
  ],
})
export class AppModule {}

If this provider is missing at runtime, calling POST /register throws:

Error: Missing provider: CREATES_NEW_USERS. Register an implementation of CreatesNewUsers.

Registration Flow

  1. Client sends POST /register with name, email, password, and passwordConfirmation.
  2. The RegisterDto validates the request body using class-validator.
  3. The RegistrationService calls your CreatesNewUsers.create() implementation.
  4. An auth.registered event is emitted (if @nestjs/event-emitter is installed).
  5. Access and refresh tokens are generated and returned to the client.

Feature.RESET_PASSWORDS

Value: "reset-passwords"

Enables the password reset flow with two endpoints: one to request a reset link, and one to reset the password using a token.

Routes Enabled

MethodRouteDescription
POST/forgot-passwordRequest a password reset token
POST/reset-passwordReset the password using a token

Services Registered

  • PasswordResetService -- generates tokens, validates them, and orchestrates the password reset.

Required Providers

This feature requires two additional providers:

1. PasswordResetRepository

A storage adapter for password reset tokens. Configure it via the passwordResetRepository option:

AuthenticationModule.forRoot({
  passwordResetRepository: TypeOrmPasswordResetRepository,
  // ...
})

The interface:

interface PasswordResetRepository {
  createToken(email: string, hashedToken: string): Promise<void>;
  findByEmail(email: string): Promise<{ token: string; createdAt: Date } | null>;
  deleteByEmail(email: string): Promise<void>;
}

2. ResetsUserPasswords

An action that performs the actual password update. Register it using the RESETS_USER_PASSWORDS token:

import { Injectable } from "@nestjs/common";
import * as bcrypt from "bcrypt";
import { ResetsUserPasswords, AuthUser, USER_REPOSITORY, UserRepository } from "@nestbolt/authentication";
import { Inject } from "@nestjs/common";

@Injectable()
export class ResetUserPassword implements ResetsUserPasswords {
  constructor(
    @Inject(USER_REPOSITORY) private userRepository: UserRepository,
  ) {}

  async reset(user: AuthUser, password: string): Promise<void> {
    const hashedPassword = await bcrypt.hash(password, 12);
    await this.userRepository.save({
      id: user.id,
      password: hashedPassword,
    });
  }
}
import { RESETS_USER_PASSWORDS } from "@nestbolt/authentication";

@Module({
  providers: [
    { provide: RESETS_USER_PASSWORDS, useClass: ResetUserPassword },
  ],
})
export class AppModule {}

Password Reset Flow

  1. Client sends POST /forgot-password with { email }.
  2. The service looks up the user by email. If found, it generates a random token, hashes it with bcrypt, and stores it via the PasswordResetRepository.
  3. The raw (unhashed) token is returned in the response. You are responsible for sending it to the user (e.g., via email).
  4. Client sends POST /reset-password with { email, token, password, passwordConfirmation }.
  5. The service looks up the stored token, verifies it has not expired (tokens expire after 60 minutes), and compares it with bcrypt.
  6. Your ResetsUserPasswords.reset() implementation is called to update the password.
  7. The stored token is deleted.
  8. An auth.password-reset event is emitted.

Feature.EMAIL_VERIFICATION

Value: "email-verification"

Enables email verification with HMAC-signed verification URLs.

Routes Enabled

MethodRouteDescription
GET/email/verify/:id/:hashVerify an email address
POST/email/verification-notificationResend the verification notification

Both routes require JWT authentication.

Services Registered

  • EmailVerificationService -- generates signed verification URLs and verifies them.

How Verification URLs Work

The service generates verification data containing:

  • id -- the user's ID
  • hash -- an HMAC-SHA256 hash of the user's email address
  • signature -- an HMAC-SHA256 signature of the id:hash:expires payload
  • expires -- a Unix timestamp (1 hour from generation)

You construct the verification URL on your side and send it to the user. When the user visits the URL, your frontend extracts the parameters and calls GET /email/verify/:id/:hash?signature=...&expires=....

// Request verification data
const response = await fetch("/email/verification-notification", {
  method: "POST",
  headers: { Authorization: `Bearer ${accessToken}` },
});
const { id, hash, signature, expires } = await response.json();

// Construct your verification URL
const verifyUrl = `https://myapp.com/verify-email?id=${id}&hash=${hash}&signature=${signature}&expires=${expires}`;

Verification Flow

  1. Authenticated user calls POST /email/verification-notification.
  2. If the user's email is already verified, a message is returned immediately.
  3. Otherwise, the service generates the verification data (id, hash, signature, expires) and returns it.
  4. You send the verification link to the user via your email service.
  5. User clicks the link. Your frontend calls GET /email/verify/:id/:hash?signature=...&expires=....
  6. The service verifies that the link has not expired, the hash matches the user's email, and the signature is valid.
  7. The user's emailVerifiedAt field is set to the current timestamp.
  8. An auth.email-verified event is emitted.

Feature.UPDATE_PROFILE_INFORMATION

Value: "update-profile-information"

Enables profile updates (name, email) for authenticated users.

Routes Enabled

MethodRouteDescription
PUT/user/profile-informationUpdate the user's profile

This route requires JWT authentication.

Services Registered

  • ProfileService -- delegates to your UpdatesUserProfile action.

Required Action Provider

Register an UpdatesUserProfile implementation using the UPDATES_USER_PROFILE token:

import { Injectable, Inject } from "@nestjs/common";
import { UpdatesUserProfile, AuthUser, USER_REPOSITORY, UserRepository } from "@nestbolt/authentication";

@Injectable()
export class UpdateUserProfile implements UpdatesUserProfile {
  constructor(
    @Inject(USER_REPOSITORY) private userRepository: UserRepository,
  ) {}

  async update(user: AuthUser, data: Record<string, any>): Promise<void> {
    await this.userRepository.save({
      id: user.id,
      name: data.name ?? user.name,
      email: data.email ?? user.email,
      // If email changed, you may want to reset emailVerifiedAt
      ...(data.email && data.email !== user.email
        ? { emailVerifiedAt: null }
        : {}),
    });
  }
}
import { UPDATES_USER_PROFILE } from "@nestbolt/authentication";

@Module({
  providers: [
    { provide: UPDATES_USER_PROFILE, useClass: UpdateUserProfile },
  ],
})
export class AppModule {}

Feature.UPDATE_PASSWORDS

Value: "update-passwords"

Enables password changes for authenticated users. This is for users who know their current password and want to change it -- distinct from the password reset flow (which is for users who forgot their password).

Routes Enabled

MethodRouteDescription
PUT/user/passwordUpdate the user's password

This route requires JWT authentication.

Services Registered

  • PasswordService -- delegates to your UpdatesUserPasswords action and emits the auth.password-updated event.

Required Action Provider

Register an UpdatesUserPasswords implementation using the UPDATES_USER_PASSWORDS token:

import { Injectable, Inject, UnprocessableEntityException } from "@nestjs/common";
import * as bcrypt from "bcrypt";
import {
  UpdatesUserPasswords,
  AuthUser,
  USER_REPOSITORY,
  UserRepository,
} from "@nestbolt/authentication";

@Injectable()
export class UpdateUserPassword implements UpdatesUserPasswords {
  constructor(
    @Inject(USER_REPOSITORY) private userRepository: UserRepository,
  ) {}

  async update(user: AuthUser, data: Record<string, any>): Promise<void> {
    const currentPasswordValid = await bcrypt.compare(
      data.currentPassword,
      user.password,
    );
    if (!currentPasswordValid) {
      throw new UnprocessableEntityException(
        "The provided password does not match your current password.",
      );
    }

    if (data.password !== data.passwordConfirmation) {
      throw new UnprocessableEntityException(
        "The password confirmation does not match.",
      );
    }

    const hashedPassword = await bcrypt.hash(data.password, 12);
    await this.userRepository.save({
      id: user.id,
      password: hashedPassword,
    });
  }
}
import { UPDATES_USER_PASSWORDS } from "@nestbolt/authentication";

@Module({
  providers: [
    { provide: UPDATES_USER_PASSWORDS, useClass: UpdateUserPassword },
  ],
})
export class AppModule {}

Feature.TWO_FACTOR_AUTHENTICATION

Value: "two-factor-authentication"

Enables a complete TOTP-based two-factor authentication system with QR codes, secret key management, confirmation flow, recovery codes, and challenge-based login.

Routes Enabled

MethodRouteDescription
POST/user/two-factor-authenticationEnable 2FA for the user
DELETE/user/two-factor-authenticationDisable 2FA for the user
POST/user/confirmed-two-factor-authenticationConfirm 2FA setup with a TOTP code
GET/user/two-factor-qr-codeGet a QR code SVG and otpauth URL
GET/user/two-factor-secret-keyGet the raw TOTP secret key
GET/user/two-factor-recovery-codesGet current recovery codes
POST/user/two-factor-recovery-codesRegenerate recovery codes
POST/two-factor-challengeComplete 2FA login with code or recovery code

All /user/* routes require JWT authentication. The /two-factor-challenge route is public (it uses the challenge token from the login response).

Services Registered

  • TwoFactorService -- manages 2FA enable/disable, code validation, QR codes, and recovery codes.
  • TwoFactorProviderService -- wraps the otplib authenticator for TOTP operations and qrcode for SVG generation.

No Additional Providers Required

Unlike other features, two-factor authentication does not require any custom action providers. Everything is handled internally by the package.

Two-Factor Login Flow

  1. User calls POST /login with their credentials.
  2. If the user has 2FA enabled, the login endpoint returns { twoFactor: true, challengeToken: "..." } instead of access/refresh tokens.
  3. The user enters their TOTP code from their authenticator app (or a recovery code).
  4. Client sends POST /two-factor-challenge with { challengeToken, code } or { challengeToken, recoveryCode }.
  5. If the code is valid, access and refresh tokens are returned.
  6. If the code is invalid, a 422 Unprocessable Entity response is returned and the attempt is counted against the rate limit.

Recovery Codes

When 2FA is enabled, 8 recovery codes are generated and stored (encrypted) in the database. Each recovery code can be used once -- after use, it is replaced with a new code. Users can regenerate all recovery codes at any time.

Configuration

See twoFactorOptions in the Configuration page for options like confirm, confirmPassword, window, and secretLength.


Always-Available Features

The following functionality is always available regardless of which features are enabled:

Core Authentication

MethodRouteDescription
POST/loginAuthenticate with username and password
POST/refreshRefresh access token using a refresh token
POST/logoutLog out (requires JWT)

Password Confirmation

MethodRouteDescription
POST/user/confirm-passwordConfirm the user's password
GET/user/confirmed-password-statusCheck if password was recently confirmed

These routes are always registered because the AuthController and ConfirmPasswordController are core components, not feature-gated.

Next Steps

  • API Routes -- complete request/response reference for all endpoints.
  • Decorators and Guards -- use the package's decorators and guards in your own controllers.