@nestbolt/authentication
Quick Start
Set up a complete authentication system in your NestJS application in minutes with @nestbolt/authentication.
This guide walks you through setting up a fully working authentication system from scratch. By the end, you will have login, registration, token refresh, and logout endpoints ready to use.
1. Define Your User Entity
The package expects your user entity to implement the AuthUser interface. Here is the interface for reference:
interface AuthUser {
id: string;
name: string;
email: string;
password: string;
emailVerifiedAt: Date | null;
twoFactorSecret: string | null;
twoFactorRecoveryCodes: string | null;
twoFactorConfirmedAt: Date | null;
passwordConfirmedAt: Date | null;
}Your entity can have additional fields beyond these. Here is an example using TypeORM:
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from "typeorm";
@Entity("users")
export class User {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column()
name: string;
@Column({ unique: true })
email: string;
@Column()
password: string;
@Column({ type: "timestamp", nullable: true })
emailVerifiedAt: Date | null;
@Column({ type: "text", nullable: true })
twoFactorSecret: string | null;
@Column({ type: "text", nullable: true })
twoFactorRecoveryCodes: string | null;
@Column({ type: "timestamp", nullable: true })
twoFactorConfirmedAt: Date | null;
@Column({ type: "timestamp", nullable: true })
passwordConfirmedAt: Date | null;
@CreateDateColumn()
createdAt: Date;
}2. Implement the UserRepository
The UserRepository interface is the only required adapter. It has four methods:
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { UserRepository, AuthUser } from "@nestbolt/authentication";
import { User } from "./user.entity";
@Injectable()
export class TypeOrmUserRepository implements UserRepository {
constructor(
@InjectRepository(User)
private readonly repo: Repository<User>,
) {}
async findById(id: string): Promise<AuthUser | null> {
return this.repo.findOneBy({ id });
}
async findByField(field: string, value: string): Promise<AuthUser | null> {
return this.repo.findOneBy({ [field]: value });
}
async save(user: Partial<AuthUser> & { id: string }): Promise<AuthUser> {
return this.repo.save(user);
}
async create(data: Omit<AuthUser, "id">): Promise<AuthUser> {
const entity = this.repo.create(data);
return this.repo.save(entity);
}
}Mongoose Example
If you are using MongoDB with Mongoose:
import { Injectable } from "@nestjs/common";
import { InjectModel } from "@nestjs/mongoose";
import { Model } from "mongoose";
import { UserRepository, AuthUser } from "@nestbolt/authentication";
import { User, UserDocument } from "./user.schema";
@Injectable()
export class MongooseUserRepository implements UserRepository {
constructor(@InjectModel(User.name) private model: Model<UserDocument>) {}
async findById(id: string): Promise<AuthUser | null> {
return this.model.findById(id).lean().exec();
}
async findByField(field: string, value: string): Promise<AuthUser | null> {
return this.model.findOne({ [field]: value }).lean().exec();
}
async save(user: Partial<AuthUser> & { id: string }): Promise<AuthUser> {
return this.model
.findByIdAndUpdate(user.id, user, { new: true })
.lean()
.exec();
}
async create(data: Omit<AuthUser, "id">): Promise<AuthUser> {
return this.model.create(data);
}
}Prisma Example
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import { UserRepository, AuthUser } from "@nestbolt/authentication";
@Injectable()
export class PrismaUserRepository implements UserRepository {
constructor(private prisma: PrismaService) {}
async findById(id: string): Promise<AuthUser | null> {
return this.prisma.user.findUnique({ where: { id } });
}
async findByField(field: string, value: string): Promise<AuthUser | null> {
return this.prisma.user.findFirst({ where: { [field]: value } });
}
async save(user: Partial<AuthUser> & { id: string }): Promise<AuthUser> {
const { id, ...data } = user;
return this.prisma.user.update({ where: { id }, data });
}
async create(data: Omit<AuthUser, "id">): Promise<AuthUser> {
return this.prisma.user.create({ data });
}
}3. Implement the CreatesNewUsers Action
For the registration feature to work, you need to provide an implementation of the CreatesNewUsers interface. This action is responsible for validating uniqueness, hashing the password, and creating the user record:
import { Injectable, ConflictException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import * as bcrypt from "bcrypt";
import { CreatesNewUsers, AuthUser } from "@nestbolt/authentication";
import { User } from "./user.entity";
@Injectable()
export class CreateNewUser implements CreatesNewUsers {
constructor(
@InjectRepository(User)
private readonly repo: Repository<User>,
) {}
async create(data: Record<string, any>): Promise<AuthUser> {
const existing = await this.repo.findOneBy({ email: data.email });
if (existing) {
throw new ConflictException("A user with this email already exists.");
}
const hashedPassword = await bcrypt.hash(data.password, 12);
const user = this.repo.create({
name: data.name,
email: data.email,
password: hashedPassword,
emailVerifiedAt: null,
twoFactorSecret: null,
twoFactorRecoveryCodes: null,
twoFactorConfirmedAt: null,
passwordConfirmedAt: null,
});
return this.repo.save(user);
}
}Register this action as a provider using the CREATES_NEW_USERS token:
import { CREATES_NEW_USERS } from "@nestbolt/authentication";
@Module({
providers: [
{
provide: CREATES_NEW_USERS,
useClass: CreateNewUser,
},
],
})
export class AppModule {}4. Generate Secrets
You need three secrets for the module configuration:
# JWT signing secret
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
# Refresh token secret (use a different value)
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
# Encryption key (must be exactly 32 bytes, base64-encoded)
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"Store these in your .env file:
JWT_SECRET=your_jwt_secret_here
REFRESH_SECRET=your_refresh_secret_here
ENCRYPTION_KEY=your_base64_encryption_key_here5. Configure the Module
Wire everything together in your application module:
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import {
AuthenticationModule,
Feature,
CREATES_NEW_USERS,
} from "@nestbolt/authentication";
import { User } from "./users/user.entity";
import { TypeOrmUserRepository } from "./users/typeorm-user.repository";
import { CreateNewUser } from "./users/create-new-user.action";
@Module({
imports: [
TypeOrmModule.forRoot({
// ... your database configuration
}),
TypeOrmModule.forFeature([User]),
AuthenticationModule.forRoot({
features: [Feature.REGISTRATION],
userRepository: TypeOrmUserRepository,
jwtSecret: process.env.JWT_SECRET!,
refreshSecret: process.env.REFRESH_SECRET!,
encryptionKey: process.env.ENCRYPTION_KEY!,
appName: "MyApp",
}),
],
providers: [
TypeOrmUserRepository,
{
provide: CREATES_NEW_USERS,
useClass: CreateNewUser,
},
],
})
export class AppModule {}6. Test It
Start your application and test the endpoints:
Register a New User
curl -X POST http://localhost:3000/register \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Doe",
"email": "jane@example.com",
"password": "secretpassword",
"passwordConfirmation": "secretpassword"
}'Response:
{
"twoFactor": false,
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}Log In
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"password": "secretpassword"
}'Response:
{
"twoFactor": false,
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}Access a Protected Route
Use the access token as a Bearer token in the Authorization header:
curl http://localhost:3000/some-protected-route \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Refresh Tokens
When the access token expires, use the refresh token to get a new pair:
curl -X POST http://localhost:3000/refresh \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}'Log Out
curl -X POST http://localhost:3000/logout \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Adding More Features
To enable additional features, add them to the features array and provide any required action implementations. See the Features page for details on what each feature requires.
AuthenticationModule.forRoot({
features: [
Feature.REGISTRATION,
Feature.RESET_PASSWORDS,
Feature.EMAIL_VERIFICATION,
Feature.UPDATE_PROFILE_INFORMATION,
Feature.UPDATE_PASSWORDS,
Feature.TWO_FACTOR_AUTHENTICATION,
],
userRepository: TypeOrmUserRepository,
jwtSecret: process.env.JWT_SECRET!,
refreshSecret: process.env.REFRESH_SECRET!,
encryptionKey: process.env.ENCRYPTION_KEY!,
appName: "MyApp",
}),Next Steps
- Configuration -- explore all module options including rate limits, token expiry, and two-factor settings.
- Features -- understand each feature toggle in detail.
- API Routes -- complete reference for all authentication endpoints.