1-creating-a-secure-api-with-nestjs-and-jwt-authentication.html

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 and passport-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.

  1. Register a User: Send a POST request to http://localhost:3000/auth/register with a JSON body: json { "username": "testuser", "password": "password" }

  2. Login: Send a POST request to http://localhost:3000/auth/login with the same credentials. You should receive a JWT in response.

  3. 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!

SR
Syed
Rizwan

About the Author

Syed Rizwan is a Machine Learning Engineer with 5 years of experience in AI, IoT, and Industrial Automation.