js

Complete NestJS Authentication Guide: JWT, Prisma, and Advanced Security Patterns

Build complete NestJS authentication with JWT, Prisma & PostgreSQL. Learn refresh tokens, RBAC, email verification, security patterns & testing for production-ready apps.

Complete NestJS Authentication Guide: JWT, Prisma, and Advanced Security Patterns

I’ve been thinking a lot about authentication systems lately—how they form the foundation of nearly every modern application, yet so many implementations cut corners on security. That’s why I want to share a comprehensive approach to building a robust authentication system using NestJS, Prisma, and JWT.

Getting started requires setting up our foundation properly. Let’s begin by creating our project structure and installing the necessary dependencies. This ensures we have everything needed for a production-ready authentication system.

npm i -g @nestjs/cli
nest new auth-system
cd auth-system
npm install @nestjs/jwt @nestjs/passport passport-jwt bcryptjs
npm install prisma @prisma/client
npx prisma init

Have you ever wondered what makes some authentication systems more secure than others? It often comes down to proper token management and database design. Let’s set up our Prisma schema to handle users, refresh tokens, and security features properly.

// schema.prisma
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  password  String
  role      String   @default("USER")
  isVerified Boolean @default(false)
  createdAt DateTime @default(now())
  refreshTokens RefreshToken[]
}

model RefreshToken {
  id     String @id @default(cuid())
  token  String @unique
  userId String
  user   User   @relation(fields: [userId], references: [id])
  expiresAt DateTime
}

Now let’s implement the core authentication service. This is where we handle password hashing, token generation, and validation. Notice how we use bcrypt for password security and implement proper error handling.

// auth.service.ts
import * as bcrypt from 'bcryptjs';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(
    private prisma: PrismaService,
    private jwtService: JwtService,
  ) {}

  async validateUser(email: string, password: string) {
    const user = await this.prisma.user.findUnique({ where: { email } });
    if (user && await bcrypt.compare(password, user.password)) {
      const { password: _, ...result } = user;
      return result;
    }
    return null;
  }

  async login(user: any) {
    const payload = { email: user.email, sub: user.id };
    return {
      access_token: this.jwtService.sign(payload),
      refresh_token: await this.generateRefreshToken(user.id),
    };
  }
}

What about handling refresh tokens securely? This is crucial for maintaining user sessions without compromising security. Let’s implement a proper refresh token mechanism that includes expiration and validation.

// refresh-token.service.ts
@Injectable()
export class RefreshTokenService {
  async generateRefreshToken(userId: string): Promise<string> {
    const token = crypto.randomBytes(40).toString('hex');
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 7); // 7 days expiration
    
    await this.prisma.refreshToken.create({
      data: {
        token,
        userId,
        expiresAt,
      },
    });
    
    return token;
  }

  async validateRefreshToken(token: string): Promise<boolean> {
    const storedToken = await this.prisma.refreshToken.findUnique({
      where: { token },
      include: { user: true },
    });
    
    return storedToken && storedToken.expiresAt > new Date();
  }
}

Role-based access control is another critical component. How do we ensure users only access what they’re supposed to? Let’s create a custom decorator and guard for handling different user roles.

// roles.decorator.ts
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

// roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.get<string[]>(
      'roles',
      context.getHandler(),
    );
    if (!requiredRoles) return true;

    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.includes(user.role);
  }
}

Security headers and rate limiting are often overlooked but essential for production systems. Let’s implement these features to protect against common attacks and abuse.

// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  app.use(helmet());
  app.use(
    rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 100, // limit each IP to 100 requests per windowMs
    }),
  );
  
  await app.listen(3000);
}

Testing is non-negotiable for authentication systems. How can we ensure our implementation works correctly under various scenarios? Here’s a basic test structure for our auth service.

// auth.service.spec.ts
describe('AuthService', () => {
  let service: AuthService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AuthService,
        { provide: PrismaService, useValue: mockPrisma },
        { provide: JwtService, useValue: mockJwtService },
      ],
    }).compile();

    service = module.get<AuthService>(AuthService);
  });

  it('should validate user credentials', async () => {
    const result = await service.validateUser('[email protected]', 'password');
    expect(result).toHaveProperty('id');
  });
});

Deployment requires careful configuration of environment variables and security settings. Remember to use different secrets for development and production, and never commit sensitive data to version control.

# .env.production
JWT_SECRET=your-production-jwt-secret
DATABASE_URL=your-production-database-url
JWT_EXPIRES_IN=15m
REFRESH_TOKEN_EXPIRES_IN=7d

Building a complete authentication system involves many moving parts, but each component plays a vital role in overall security. From proper password hashing to secure token management and role-based access control, every detail matters.

What security measures have you found most effective in your projects? I’d love to hear about your experiences and recommendations. If you found this guide helpful, please share it with others who might benefit from it, and feel free to leave your thoughts in the comments below.

Keywords: NestJS authentication tutorial, JWT authentication NestJS, Prisma ORM integration, Node.js authentication system, NestJS Prisma JWT, role-based access control RBAC, NestJS security best practices, TypeScript authentication API, NestJS refresh tokens, PostgreSQL authentication tutorial



Similar Posts
Blog Image
Next.js Prisma Integration Guide: Build Type-Safe Full-Stack Applications with Modern ORM

Learn how to integrate Next.js with Prisma ORM for type-safe, scalable web applications. Complete guide to setup, migrations & best practices.

Blog Image
Complete Guide to Next.js Prisma Integration: Build Type-Safe Full-Stack Applications in 2024

Learn how to integrate Next.js with Prisma ORM for type-safe, full-stack web development. Build powerful database-driven apps with seamless TypeScript integration.

Blog Image
Build Real-Time Analytics Dashboard: WebSockets, Redis Streams & React Query Performance Guide

Build high-performance real-time analytics dashboards using WebSockets, Redis Streams & React Query. Learn data streaming, optimization & production strategies.

Blog Image
Build Real-time Collaborative Document Editor: Socket.io, Operational Transform & MongoDB Complete Tutorial

Build real-time collaborative document editor with Socket.io, Operational Transform & MongoDB. Learn conflict resolution, cursor tracking & performance optimization for concurrent editing.

Blog Image
Complete Guide to Integrating Next.js with Prisma ORM for Type-Safe Database Applications

Learn how to integrate Next.js with Prisma ORM for type-safe, full-stack web applications. Complete guide with setup, best practices & real examples.

Blog Image
Build High-Performance GraphQL APIs: TypeScript, Apollo Server, and DataLoader Pattern Guide

Learn to build high-performance GraphQL APIs with TypeScript, Apollo Server & DataLoader. Solve N+1 queries, optimize database performance & implement caching strategies.