creating-a-secure-api-with-jwt-authentication-in-nestjs.html

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!

SR
Syed
Rizwan

About the Author

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