Learn how to create a simple authentication system in NestJS using Passport.
How to Create an Authentication System Using NestJS
In this tutorial, I'll walk you through the process of setting up a basic authentication system using NestJS, Passport, and the passport-local strategy. We'll then add a refresh token endpoint to keep your users securely logged in while keeping your JWT token expiration short. Let's get started!
Prerequisites
Before we dive into the code, make sure you have these packages installed:
@nestjs/passport
passport
passport-local
You can install them using npm like this:
npm install @nestjs/passport passport passport-local
Setting Up NestJS
First, let's set up our NestJS project. If you haven't created one yet, you can do so with the Nest CLI:
nest new auth-system
cd auth-system
Create the Auth Module
We'll start by creating an AuthModule
that will house all our authentication logic.
nest generate module auth
Create the User Service
Next, let's create a service to manage our users. For simplicity, we'll use an in-memory array of users.
nest generate service users
In users.service.ts
, add the following code:
import { Injectable } from "@nestjs/common";
import { User } from "./user.interface";
@Injectable()
export class UsersService {
// Mock list of users
private readonly users: User[] = [
{ userId: 1, username: "raymond", password: "passwordForRaymond" },
{ userId: 2, username: "liz", password: "passwordForLiz" },
{ userId: 3, username: "donald", password: "passwordForDonald" },
];
async findOne(username: string): Promise<User | undefined> {
return this.users.find((user) => user.username === username);
}
}
Create the Auth Service
Now, let's create a service to handle our authentication logic.
nest generate service auth
In auth.service.ts
, add the following code:
import { Injectable } from "@nestjs/common";
import { UsersService } from "../users/users.service";
import { JwtService } from "@nestjs/jwt";
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService
) {}
async validateUser(username: string, pass: string): Promise<any> {
const user = await this.usersService.findOne(username);
if (user && user.password === pass) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
const payload = { username: user.username, sub: user.userId };
return {
access_token: this.jwtService.sign(payload),
};
}
// Add a method for refreshing tokens
async refresh(token: string) {
const decoded = this.jwtService.verify(token);
const payload = { username: decoded.username, sub: decoded.sub };
return {
access_token: this.jwtService.sign(payload),
};
}
}
Setting Up Local Strategy
We'll use the passport-local
strategy for username and password authentication.
nest generate class auth/local.strategy
In local.strategy.ts
, add the following code:
import { Strategy } from "passport-local";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { AuthService } from "./auth.service";
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Setting Up Local Auth Guard
We'll need a guard to handle the local strategy.
nest generate class auth/local-auth.guard
In local-auth.guard.ts
, add the following code:
import { Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
@Injectable()
export class LocalAuthGuard extends AuthGuard("local") {}
Setting Up Auth Controller
Now, let's create a controller to handle authentication requests.
nest generate controller auth
In auth.controller.ts
, add the following code:
Notice that we added @UseGuards(LocalAuthGuard)
to the login
endpoint which triggers our local auth strategy.
import { Controller, Request, Post, UseGuards } from "@nestjs/common";
import { AuthService } from "./auth.service";
import { LocalAuthGuard } from "./local-auth.guard";
@Controller("auth")
export class AuthController {
constructor(private authService: AuthService) {}
@UseGuards(LocalAuthGuard)
@Post("login")
async login(@Request() req) {
return this.authService.login(req.user);
}
@Post("refresh")
async refresh(@Request() req) {
const { refresh_token } = req.body;
return this.authService.refresh(refresh_token);
}
}
Adding JWT Support
Let's add JWT support to our application. First, install the necessary packages:
npm install @nestjs/jwt passport-jwt
Update auth.module.ts
to include JWT configuration:
import { Module } from "@nestjs/common";
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { AuthService } from "./auth.service";
import { LocalStrategy } from "./local.strategy";
import { UsersModule } from "../users/users.module";
import { AuthController } from "./auth.controller";
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: "secretKey", // Use a more secure key in production
signOptions: { expiresIn: "60s" }, // Token expiration time
}),
],
providers: [AuthService, LocalStrategy],
controllers: [AuthController],
})
export class AuthModule {}
Putting It All Together
Make sure to import the AuthModule
in your main application module (app.module.ts
).
import { Module } from "@nestjs/common";
import { AuthModule } from "./auth/auth.module";
import { UsersModule } from "./users/users.module";
@Module({
imports: [AuthModule, UsersModule],
})
export class AppModule {}
Testing the Authentication System
Start your NestJS application:
npm run start
You can test your authentication system using tools like Postman or Insomnia. Here's how to do it:
- Login: Send a POST request to
http://localhost:3000/auth/login
with a JSON body containingusername
andpassword
.
curl -XPOST -H "Content-type: application/json" -d '{
"username": "raymond",
"password": "passwordForRaymond"
}' 'http://localhost:3000/auth/login'
- Refresh Token: Send a POST request to
http://localhost:3000/auth/refresh
with a JSON body containingrefresh_token
.
curl -XPOST -H "Content-type: application/json" -d '{
"refresh_token": "your_refresh_token_here"
}' 'http://localhost:3000/auth/refresh'
And that's it! You've successfully created an authentication system using NestJS, Passport, and JWT. This setup provides a starting point that you can build upon to add more advanced features like role-based access control, social logins, and more.