Creating a Secure API with NestJS and JWT Authentication
In today's digital landscape, building secure applications is more crucial than ever. When developing APIs, implementing robust authentication mechanisms is essential. One of the most popular methods for securing APIs is using JSON Web Tokens (JWT). In this article, we will explore how to create a secure API using NestJS, a powerful Node.js framework, combined with JWT authentication. We will cover everything from setting up your NestJS project to implementing JWT authentication, along with actionable insights and code examples.
What is NestJS?
NestJS is a progressive Node.js framework designed for building efficient, reliable, and scalable server-side applications. It leverages TypeScript and incorporates design patterns such as Dependency Injection, making it a favorite among developers for structuring complex applications.
Why Use JWT for Authentication?
JSON Web Tokens (JWT) are an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. Here’s why JWT is widely adopted:
- Stateless: JWTs do not require server-side sessions; all the necessary information is contained within the token.
- Compact: They can be sent via URL, POST parameter, or inside an HTTP header.
- Secure: JWTs can be signed and optionally encrypted, ensuring data integrity and confidentiality.
Setting Up Your NestJS Project
To get started, ensure you have Node.js and npm installed on your machine. Follow these steps to set up your NestJS project:
Step 1: Install NestJS CLI
npm install -g @nestjs/cli
Step 2: Create a New Project
nest new jwt-auth-demo
cd jwt-auth-demo
Step 3: Install Necessary Dependencies
To implement JWT authentication, you’ll need to install the following packages:
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
@nestjs/jwt
: NestJS module for JWT authentication.@nestjs/passport
: NestJS module for Passport integration.passport
andpassport-jwt
: Middleware for authenticating with JWT.bcrypt
: Library for hashing passwords.
Implementing JWT Authentication
Step 4: Create User Module
First, let’s create a user module to handle user registration and authentication.
nest generate module users
nest generate service users
nest generate controller users
Step 5: User Service
In users/user.service.ts
, implement user registration and retrieval:
import { Injectable } from '@nestjs/common';
import { User } from './user.entity'; // Assume you have a User entity
import * as bcrypt from 'bcrypt';
@Injectable()
export class UsersService {
private users: User[] = []; // This should be replaced with a database
async register(username: string, password: string): Promise<User> {
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = { username, password: hashedPassword }; // Replace with actual User entity
this.users.push(newUser);
return newUser;
}
async findByUsername(username: string): Promise<User | undefined> {
return this.users.find(user => user.username === username);
}
}
Step 6: User Controller
In users/user.controller.ts
, create endpoints for registration and login:
import { Controller, Post, Body } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('auth')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post('register')
async register(@Body() body: { username: string; password: string }) {
return this.usersService.register(body.username, body.password);
}
@Post('login')
async login(@Body() body: { username: string; password: string }) {
const user = await this.usersService.findByUsername(body.username);
if (user && await bcrypt.compare(body.password, user.password)) {
return { message: 'Login successful' }; // JWT token will be generated later
}
throw new Error('Invalid credentials');
}
}
Step 7: JWT Strategy
Create a JWT strategy to handle token verification. Create a new file 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(),
secretOrKey: 'your_jwt_secret', // Use environment variables for production
});
}
async validate(payload: any) {
return this.usersService.findByUsername(payload.username);
}
}
Step 8: Auth Module
Create an auth module to tie everything together:
nest generate module auth
In auth/auth.module.ts
, import necessary modules:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { UsersModule } from '../users/users.module';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
UsersModule,
JwtModule.register({
secret: 'your_jwt_secret', // Use environment variables
signOptions: { expiresIn: '60s' }, // Token expiration
}),
],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
Step 9: Auth Service
Implement the AuthService
to generate JWT tokens:
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 login(username: string, password: string) {
const user = await this.usersService.findByUsername(username);
if (user && await bcrypt.compare(password, user.password)) {
const payload = { username: user.username };
return {
access_token: this.jwtService.sign(payload),
};
}
throw new Error('Invalid credentials');
}
}
Step 10: Update Login Endpoint
Update the login endpoint in users/user.controller.ts
to return the JWT token:
@Post('login')
async login(@Body() body: { username: string; password: string }) {
return this.authService.login(body.username, body.password);
}
Testing Your API
You can test your API using tools like Postman or Insomnia.
-
Register a User: Send a POST request to
http://localhost:3000/auth/register
with a JSON body:json { "username": "testuser", "password": "password" }
-
Login: Send a POST request to
http://localhost:3000/auth/login
with the same credentials. You should receive a JWT in response. -
Access Protected Route: Create a protected route that requires authentication by using the
@UseGuards(AuthGuard('jwt'))
decorator.
Conclusion
In this article, we've covered the essential steps to create a secure API with NestJS using JWT authentication. We discussed the advantages of using JWT, set up a NestJS project, and implemented user registration and login functionalities. You now have the foundational knowledge to expand upon this by adding more features like user roles, password reset functionality, and integrating a database.
As you move forward, remember to always prioritize security in your applications. Happy coding!