Building a Secure REST API with NestJS and JWT Authentication
In today's digital landscape, creating secure applications is paramount. One common requirement for modern web apps is to implement a robust REST API that ensures data protection and user authentication. In this article, we will explore how to build a secure REST API using NestJS, a progressive Node.js framework, along with JSON Web Tokens (JWT) for authentication.
What is NestJS?
NestJS is a powerful framework for building efficient, reliable, and scalable server-side applications. It is built with TypeScript and leverages the power of decorators and modules, making it an ideal choice for creating structured applications. With its modular architecture, developers can easily manage dependencies and keep code organized.
Why Use JWT for Authentication?
JSON Web Tokens (JWT) is an open standard for securely transmitting information between parties as a JSON object. Here are some key reasons to use JWT for authentication:
- Stateless: JWTs are self-contained; they include all the necessary information about the user, eliminating the need for server-side sessions.
- Compact: They are compact and can be sent via URLs, POST parameters, or inside HTTP headers.
- Secure: When signed, JWTs provide a way to ensure the integrity of the data, making them a secure option.
Setting Up Your NestJS Project
To get started, you need to have Node.js and npm installed. Once you have these prerequisites, follow these steps:
Step 1: Create a New NestJS Project
Run the following command to install the NestJS CLI globally:
npm install -g @nestjs/cli
Now, create a new project:
nest new jwt-auth-example
Navigate to the project directory:
cd jwt-auth-example
Step 2: Install Required Packages
We need to install a few packages, including @nestjs/jwt
for JWT handling and @nestjs/passport
for authentication strategies:
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcryptjs
Step 3: Create User Module
Create a user module to handle user registration and retrieval:
nest generate module users
nest generate service users
nest generate controller users
Step 4: User Entity
In users/users.service.ts
, let's create a simple in-memory user store. For production, you would typically connect to a database.
import { Injectable } from '@nestjs/common';
import { User } from './user.entity'; // Create a User interface or class
@Injectable()
export class UsersService {
private readonly users: User[] = [];
create(user: User) {
this.users.push(user);
}
findOne(username: string): User | undefined {
return this.users.find(user => user.username === username);
}
}
Step 5: Authentication Module
Now let’s create the authentication module. Generate the module and service:
nest generate module auth
nest generate service auth
Step 6: Configure JWT
In auth/auth.service.ts
, we will set up JWT functionality:
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
@Injectable()
export class AuthService {
constructor(private usersService: UsersService, private jwtService: JwtService) {}
async validateUser(username: string, password: string): Promise<any> {
const user = this.usersService.findOne(username);
if (user && user.password === password) { // Consider hashing passwords
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),
};
}
}
Step 7: Implement JWT Strategy
Create a JWT strategy in auth/jwt.strategy.ts
:
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { UsersService } from '../users/users.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private usersService: UsersService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'your_secret_key', // Use environment variables in production
});
}
async validate(payload: any) {
return this.usersService.findOne(payload.username);
}
}
Step 8: Set Up Routes for Authentication
In auth/auth.controller.ts
, we will create authentication routes:
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('login')
async login(@Body() user: any) {
return this.authService.login(user);
}
}
Step 9: Protect Routes with Guards
To protect your API routes, use guards. In your controller, you can apply the @UseGuards(AuthGuard('jwt'))
decorator to secure specific endpoints.
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller('protected')
export class ProtectedController {
@UseGuards(AuthGuard('jwt'))
@Get()
getProtectedData() {
return { message: 'This is protected data.' };
}
}
Conclusion
Building a secure REST API with NestJS and JWT authentication helps ensure that your application is robust and user data is protected. By following the steps outlined in this guide, you can set up a basic user authentication system. As you expand your application, consider implementing features such as password hashing, user roles, and more complex data validation.
With NestJS's powerful features and JWT's robust framework for authentication, you are well on your way to creating secure and efficient web applications. Happy coding!