Frontend-agnostic authentication backend for NestJS
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.
- Installation
- Quick Start
- Module Configuration
- Features
- Database Adapters
- API Routes
- Events
- Configuration Options
- Testing
- Changelog
- Contributing
- License
# pnpm
pnpm add @nestbolt/authentication
# npm
npm install @nestbolt/authentication
# yarn
yarn add @nestbolt/authenticationpnpm add @nestjs/passport @nestjs/jwt passport passport-jwt passport-local class-validator class-transformer reflect-metadataOptional:
pnpm add @nestjs/event-emitter- Implement the
UserRepositoryinterface for your database:
import{Injectable}from"@nestjs/common";import{UserRepository,AuthUser}from"@nestbolt/authentication";
@Injectable()exportclassMyUserRepositoryimplementsUserRepository{asyncfindById(id: string): Promise<AuthUser|null>{/* ... */}asyncfindByField(field: string,value: string): Promise<AuthUser|null>{/* ... */}asyncsave(user: Partial<AuthUser>&{id: string}): Promise<AuthUser>{/* ... */}asynccreate(data: Omit<AuthUser,"id">): Promise<AuthUser>{/* ... */}}- Import
AuthenticationModulein 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 base64appName: "MyApp",}),],})exportclassAppModule{}- That's it! All 19 auth routes are now available.
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",});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"),}),});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 |
The package is database-agnostic. Implement UserRepository and optionally PasswordResetRepository for any database:
@Injectable()exportclassTypeOrmUserRepositoryimplementsUserRepository{constructor(@InjectRepository(User)privaterepo: Repository<User>){}findById(id: string){returnthis.repo.findOneBy({ id });}findByField(field: string,value: string){returnthis.repo.findOneBy({[field]: value});}save(user){returnthis.repo.save(user);}create(data){returnthis.repo.save(this.repo.create(data));}}@Injectable()exportclassMongooseUserRepositoryimplementsUserRepository{constructor(@InjectModel(User.name)privatemodel: Model<UserDocument>){}findById(id: string){returnthis.model.findById(id).lean().exec();}findByField(field: string,value: string){returnthis.model.findOne({[field]: value}).lean().exec();}save(user){returnthis.model.findByIdAndUpdate(user.id,user,{new: true}).lean().exec();}create(data){returnthis.model.create(data);}}Same pattern - implement the interface for your ORM/driver.
| 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 |
Subscribe to authentication events using @nestjs/event-emitter:
import{OnEvent}from"@nestjs/event-emitter";import{AUTH_EVENTS,UserEvent}from"@nestbolt/authentication";
@Injectable()exportclassAuthListener{
@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
| 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 |
pnpm test# Run tests
pnpm test:watch # Watch mode
pnpm test:cov # Coverage reportSee CHANGELOG.md.
See CONTRIBUTING.md.
For security-related issues, please use the security label on GitHub Issues.
- Inspired by Laravel Fortify by Taylor Otwell