Skip to content

Repository files navigation

@nestbolt/authentication

Frontend-agnostic authentication backend for NestJS

NPM VersionNPM DownloadsTestsLicense


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()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>{/* ... */}}
  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 base64appName: "MyApp",}),],})exportclassAppModule{}
  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:

FeatureDescription
Feature.REGISTRATIONUser registration (POST /register)
Feature.RESET_PASSWORDSPassword reset flow (POST /forgot-password, POST /reset-password)
Feature.EMAIL_VERIFICATIONEmail verification (GET /email/verify/:id/:hash)
Feature.UPDATE_PROFILE_INFORMATIONProfile updates (PUT /user/profile-information)
Feature.UPDATE_PASSWORDSPassword updates (PUT /user/password)
Feature.TWO_FACTOR_AUTHENTICATIONFull 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()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));}}

Mongoose (MongoDB)

@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);}}

Prisma, MikroORM, DynamoDB, etc.

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

API Routes

MethodRouteDescriptionAuth
POST/loginAuthenticate userNo
POST/refreshRefresh access tokenRefresh Token
POST/logoutLog outJWT
POST/registerCreate new userNo
POST/forgot-passwordSend reset linkNo
POST/reset-passwordReset passwordNo
GET/email/verify/:id/:hashVerify emailJWT
POST/email/verification-notificationResend verificationJWT
PUT/user/profile-informationUpdate profileJWT
PUT/user/passwordChange passwordJWT
POST/user/confirm-passwordConfirm passwordJWT
GET/user/confirmed-password-statusCheck confirmationJWT
POST/user/two-factor-authenticationEnable 2FAJWT
DELETE/user/two-factor-authenticationDisable 2FAJWT
POST/user/confirmed-two-factor-authenticationConfirm 2FA setupJWT
GET/user/two-factor-qr-codeGet QR code SVGJWT
GET/user/two-factor-secret-keyGet TOTP secretJWT
GET/POST/user/two-factor-recovery-codesGet/regenerate codesJWT
POST/two-factor-challengeComplete 2FA loginNo

Events

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

Configuration Options

OptionTypeDefaultDescription
featuresFeature[]requiredEnabled features
userRepositoryType<UserRepository>requiredUser repository class
passwordResetRepositoryType<PasswordResetRepository>-Password reset token storage
jwtSecretstringrequiredJWT signing secret
refreshSecretstringrequiredRefresh token secret
encryptionKeystringrequired32-byte base64 key for 2FA encryption
jwtExpiresInstring"15m"Access token TTL
refreshExpiresInstring"7d"Refresh token TTL
usernameFieldstring"email"Login username field
lowercaseUsernamesbooleantrueLowercase 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
passwordTimeoutnumber900Password confirmation timeout (seconds)
appNamestring"NestBolt"App name for TOTP QR codes
twoFactorOptions.confirmbooleanfalseRequire 2FA confirmation step
twoFactorOptions.confirmPasswordbooleanfalseRequire password before 2FA changes
twoFactorOptions.windownumber1TOTP time window
twoFactorOptions.secretLengthnumber20TOTP 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

About

Frontend-agnostic authentication backend for NestJS

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages