Skip to content

nestbolt/authentication

Repository files navigation

@nestbolt/authentication

Frontend-agnostic authentication backend for NestJS

NPM Version NPM Downloads Tests License


A complete, database-agnostic authentication backend for NestJS with support for registration, login, password reset, email verification, profile management, password confirmation, and two-factor authentication (TOTP).

Inspired by Laravel Fortify.

Table of Contents

Installation

# pnpm
pnpm add @nestbolt/authentication

# npm
npm install @nestbolt/authentication

# yarn
yarn add @nestbolt/authentication

Peer Dependencies

pnpm add @nestjs/passport @nestjs/jwt passport passport-jwt passport-local class-validator class-transformer reflect-metadata

Optional:

pnpm add @nestjs/event-emitter

Quick Start

  1. Implement the UserRepository interface for your database:
import { Injectable } from "@nestjs/common";
import { UserRepository, AuthUser } from "@nestbolt/authentication";

@Injectable()
export class MyUserRepository implements UserRepository {
  async findById(id: string): Promise<AuthUser | null> {
    /* ... */
  }
  async findByField(field: string, value: string): Promise<AuthUser | null> {
    /* ... */
  }
  async save(user: Partial<AuthUser> & { id: string }): Promise<AuthUser> {
    /* ... */
  }
  async create(data: Omit<AuthUser, "id">): Promise<AuthUser> {
    /* ... */
  }
}
  1. Import AuthenticationModule in your app module:
import { AuthenticationModule, Feature } from "@nestbolt/authentication";
import { MyUserRepository } from "./my-user.repository";

@Module({
  imports: [
    AuthenticationModule.forRoot({
      features: [
        Feature.REGISTRATION,
        Feature.RESET_PASSWORDS,
        Feature.EMAIL_VERIFICATION,
        Feature.UPDATE_PROFILE_INFORMATION,
        Feature.UPDATE_PASSWORDS,
        Feature.TWO_FACTOR_AUTHENTICATION,
      ],
      userRepository: MyUserRepository,
      jwtSecret: process.env.JWT_SECRET!,
      refreshSecret: process.env.REFRESH_SECRET!,
      encryptionKey: process.env.ENCRYPTION_KEY!, // 32-byte base64
      appName: "MyApp",
    }),
  ],
})
export class AppModule {}
  1. That's it! All 19 auth routes are now available.

Module Configuration

Synchronous

AuthenticationModule.forRoot({
  features: [Feature.REGISTRATION, Feature.TWO_FACTOR_AUTHENTICATION],
  userRepository: TypeOrmUserRepository,
  passwordResetRepository: TypeOrmPasswordResetRepository,
  jwtSecret: "your-jwt-secret",
  refreshSecret: "your-refresh-secret",
  encryptionKey: "base64-encoded-32-byte-key",
  appName: "MyApp",
});

Asynchronous

AuthenticationModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    features: [Feature.REGISTRATION, Feature.TWO_FACTOR_AUTHENTICATION],
    userRepository: TypeOrmUserRepository,
    jwtSecret: config.get("JWT_SECRET"),
    refreshSecret: config.get("REFRESH_SECRET"),
    encryptionKey: config.get("ENCRYPTION_KEY"),
  }),
});

Features

Enable or disable features via the features array:

Feature Description
Feature.REGISTRATION User registration (POST /register)
Feature.RESET_PASSWORDS Password reset flow (POST /forgot-password, POST /reset-password)
Feature.EMAIL_VERIFICATION Email verification (GET /email/verify/:id/:hash)
Feature.UPDATE_PROFILE_INFORMATION Profile updates (PUT /user/profile-information)
Feature.UPDATE_PASSWORDS Password updates (PUT /user/password)
Feature.TWO_FACTOR_AUTHENTICATION Full 2FA with TOTP, QR codes, and recovery codes

Database Adapters

The package is database-agnostic. Implement UserRepository and optionally PasswordResetRepository for any database:

TypeORM (SQL)

@Injectable()
export class TypeOrmUserRepository implements UserRepository {
  constructor(@InjectRepository(User) private repo: Repository<User>) {}
  findById(id: string) {
    return this.repo.findOneBy({ id });
  }
  findByField(field: string, value: string) {
    return this.repo.findOneBy({ [field]: value });
  }
  save(user) {
    return this.repo.save(user);
  }
  create(data) {
    return this.repo.save(this.repo.create(data));
  }
}

Mongoose (MongoDB)

@Injectable()
export class MongooseUserRepository implements UserRepository {
  constructor(@InjectModel(User.name) private model: Model<UserDocument>) {}
  findById(id: string) {
    return this.model.findById(id).lean().exec();
  }
  findByField(field: string, value: string) {
    return this.model
      .findOne({ [field]: value })
      .lean()
      .exec();
  }
  save(user) {
    return this.model.findByIdAndUpdate(user.id, user, { new: true }).lean().exec();
  }
  create(data) {
    return this.model.create(data);
  }
}

Prisma, MikroORM, DynamoDB, etc.

Same pattern - implement the interface for your ORM/driver.

API Routes

Method Route Description Auth
POST /login Authenticate user No
POST /refresh Refresh access token Refresh Token
POST /logout Log out JWT
POST /register Create new user No
POST /forgot-password Send reset link No
POST /reset-password Reset password No
GET /email/verify/:id/:hash Verify email JWT
POST /email/verification-notification Resend verification JWT
PUT /user/profile-information Update profile JWT
PUT /user/password Change password JWT
POST /user/confirm-password Confirm password JWT
GET /user/confirmed-password-status Check confirmation JWT
POST /user/two-factor-authentication Enable 2FA JWT
DELETE /user/two-factor-authentication Disable 2FA JWT
POST /user/confirmed-two-factor-authentication Confirm 2FA setup JWT
GET /user/two-factor-qr-code Get QR code SVG JWT
GET /user/two-factor-secret-key Get TOTP secret JWT
GET/POST /user/two-factor-recovery-codes Get/regenerate codes JWT
POST /two-factor-challenge Complete 2FA login No

Events

Subscribe to authentication events using @nestjs/event-emitter:

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

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

Available events: auth.login, auth.logout, auth.registered, auth.lockout, auth.password-reset, auth.password-updated, auth.email-verified, auth.two-factor-enabled, auth.two-factor-disabled, auth.two-factor-confirmed, auth.two-factor-challenged, auth.two-factor-failed, auth.valid-two-factor-code, auth.recovery-code-replaced, auth.recovery-codes-generated

Configuration Options

Option Type Default Description
features Feature[] required Enabled features
userRepository Type<UserRepository> required User repository class
passwordResetRepository Type<PasswordResetRepository> - Password reset token storage
jwtSecret string required JWT signing secret
refreshSecret string required Refresh token secret
encryptionKey string required 32-byte base64 key for 2FA encryption
jwtExpiresIn string "15m" Access token TTL
refreshExpiresIn string "7d" Refresh token TTL
usernameField string "email" Login username field
lowercaseUsernames boolean true Lowercase usernames on login
loginRateLimit { ttl, limit } { 60000, 5 } Login rate limiting
twoFactorRateLimit { ttl, limit } { 60000, 5 } Two-factor challenge rate limiting
verificationRateLimit { ttl, limit } { 60000, 6 } Email verification rate limiting
passwordTimeout number 900 Password confirmation timeout (seconds)
appName string "NestBolt" App name for TOTP QR codes
twoFactorOptions.confirm boolean false Require 2FA confirmation step
twoFactorOptions.confirmPassword boolean false Require password before 2FA changes
twoFactorOptions.window number 1 TOTP time window
twoFactorOptions.secretLength number 20 TOTP secret key length

Testing

pnpm test          # Run tests
pnpm test:watch    # Watch mode
pnpm test:cov      # Coverage report

Changelog

See CHANGELOG.md.

Contributing

See CONTRIBUTING.md.

Security

For security-related issues, please use the security label on GitHub Issues.

Credits

License

MIT License