Creating a Secure API with JWT Authentication in NestJS
In today’s digital landscape, ensuring the security of your applications is paramount. One of the most effective ways to secure your API is through JSON Web Tokens (JWT) authentication. In this article, we’ll dive into how to create a secure API using JWT authentication in NestJS, a powerful Node.js framework. Whether you’re building a simple application or a complex microservice architecture, understanding JWT and its implementation in NestJS will enhance the security of your projects.
What is JWT?
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with HMAC algorithm) or a public/private key pair using RSA or ECDSA.
Use Cases for JWT
- Authentication: JWTs are widely used for user authentication. After a user logs in, a JWT is generated and sent to the client. The client then includes this token in the header of subsequent requests.
- Authorization: JWTs can also be used to authorize users for specific actions, granting or denying access based on their roles or permissions.
- Information Exchange: Due to its self-contained nature, JWT can safely carry information between parties without the need for additional database lookups.
Setting Up Your NestJS Project
Before we dive into JWT implementation, let’s set up a basic NestJS project. If you haven’t already, make sure you have Node.js installed on your machine.
Step 1: Create a New NestJS Application
Run the following command to create a new NestJS project:
npm i -g @nestjs/cli
nest new jwt-auth-demo
After the installation, navigate into your project directory:
cd jwt-auth-demo
Step 2: Install Required Dependencies
We need several packages to implement JWT authentication:
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
@nestjs/jwt
: NestJS module for JWT.@nestjs/passport
: NestJS module for Passport, which is used for authentication.passport
: Authentication middleware.passport-jwt
: Passport strategy for JWT.bcrypt
: Library to hash passwords.
Implementing JWT Authentication
Step 3: Create User Module
First, let’s create a user module that will handle user registration and login.
nest generate module users
nest generate service users
nest generate controller users
Step 4: User Service
In users.service.ts
, implement basic user registration and login functionality. For simplicity, we will use an in-memory array to store users.
import { Injectable } from '@nestjs/common';
import { User } from './user.interface'; // Create a User interface
import * as bcrypt from 'bcrypt';
@Injectable()
export class UsersService {
private readonly users: User[] = [];
async create(user: User): Promise<void> {
const hashedPassword = await bcrypt.hash(user.password, 10);
this.users.push({ ...user, password: hashedPassword });
}
async findOne(username: string): Promise<User | undefined> {
return this.users.find(user => user.username === username);
}
}
Step 5: User Controller
In users.controller.ts
, add endpoints for user registration and login.
import { Body, Controller, Post } from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from './user.interface';
@Controller('auth')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post('register')
async register(@Body() user: User): Promise<void> {
await this.usersService.create(user);
}
@Post('login')
async login(@Body() user: User): Promise<string> {
const existingUser = await this.usersService.findOne(user.username);
if (existingUser && await bcrypt.compare(user.password, existingUser.password)) {
// Generate JWT token
return this.generateToken(existingUser.username);
}
throw new Error('Invalid credentials');
}
private generateToken(username: string): string {
// Token generation logic will be implemented later
return '';
}
}
Step 6: JWT Strategy
Now, let’s create the JWT strategy. Create a new file named jwt.strategy.ts
in the auth
directory.
import { Injectable } from '@nestjs/common';
import { JwtStrategy } from '@nestjs/jwt';
import { ExtractJwt } from 'passport-jwt';
import { UsersService } from '../users/users.service';
@Injectable()
export class JwtAuthStrategy extends JwtStrategy {
constructor(private readonly usersService: UsersService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'YOUR_SECRET_KEY', // Use an environment variable in production
});
}
async validate(payload: any) {
return this.usersService.findOne(payload.username);
}
}
Step 7: Update the App Module
In app.module.ts
, import the necessary modules and configure JWT.
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { UsersModule } from './users/users.module';
import { JwtAuthStrategy } from './auth/jwt.strategy';
@Module({
imports: [
UsersModule,
JwtModule.register({
secret: 'YOUR_SECRET_KEY',
signOptions: { expiresIn: '60s' }, // Token expiration
}),
],
providers: [JwtAuthStrategy],
})
export class AppModule {}
Step 8: Generate and Return JWT Token
Finally, implement the token generation logic in the login
method of the UsersController
.
import { JwtService } from '@nestjs/jwt';
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
) {}
private generateToken(username: string): string {
const payload = { username };
return this.jwtService.sign(payload);
}
Step 9: Protecting Routes
To protect specific routes, use the @UseGuards
decorator along with Passport’s JWT strategy.
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from './jwt-auth.guard';
@Controller('protected')
export class ProtectedController {
@UseGuards(JwtAuthGuard)
@Get()
getProtectedResource() {
return { message: 'This is a protected resource' };
}
}
Conclusion
By following the steps outlined in this article, you have successfully created a secure API using JWT authentication in NestJS. You’ve learned how to set up user registration and login, generate JWT tokens, and protect routes using guards.
As you continue to develop your applications, remember that security is an ongoing process. Regularly update your dependencies, manage your secrets carefully, and stay informed about the latest security practices.
With NestJS and JWT, you’re well on your way to building robust and secure applications. Happy coding!