Implementing JWT Authentication in a NestJS API with TypeScript
Introduction
In the world of web development, security is paramount, especially when dealing with user authentication. JSON Web Tokens (JWT) have become a popular choice for securing APIs due to their efficiency and ease of use. In this article, we will explore how to implement JWT authentication in a NestJS API using TypeScript. We’ll cover everything from setting up your NestJS project to creating a fully functional authentication system. Whether you're a beginner or an experienced developer, this guide will provide you with actionable insights and clear code examples.
What is JWT?
JWT, or JSON Web Token, is an open standard (RFC 7519) that defines a compact way to represent claims securely between two parties. A JWT is a string made up of three parts: the header, payload, and signature. This structure allows you to verify the integrity of the data and authenticate users across your application.
Use Cases for JWT
- Stateless Authentication: JWTs can be used in stateless APIs where the server does not need to store session data.
- Single Sign-On (SSO): JWTs are ideal for SSO implementations since they can be shared across different domains.
- Mobile Applications: JWTs are lightweight and can easily be sent in HTTP headers, making them suitable for mobile app authentication.
Setting Up a NestJS Project
Before we dive into JWT implementation, let’s create a new NestJS project. If you haven’t installed the Nest CLI, you can do so with the following command:
npm i -g @nestjs/cli
Now, create a new project:
nest new jwt-auth-example
cd jwt-auth-example
Installing Required Packages
To implement JWT authentication, you’ll need to install several packages:
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
- @nestjs/jwt: Provides JWT utilities for NestJS.
- @nestjs/passport: Integrates Passport.js with NestJS.
- passport: A popular authentication middleware for Node.js.
- passport-jwt: A Passport strategy for JWT authentication.
- bcrypt: For hashing passwords.
Creating the Authentication Module
Let’s create an authentication module to handle user login and JWT creation. First, generate the module and service:
nest generate module auth
nest generate service auth
Now, we will implement the authentication logic in auth.service.ts
. Here’s how you can set it up:
// auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { User } from './user.entity'; // Assume you have a User entity
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(private readonly jwtService: JwtService) {}
async validateUser(username: string, password: string): Promise<any> {
const user = await this.findUserByUsername(username); // Implement user fetching
if (user && await bcrypt.compare(password, user.password)) {
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),
};
}
private async findUserByUsername(username: string): Promise<User | null> {
// Implement this to fetch user from your database
return null;
}
}
Implementing the JWT Strategy
Next, we need to create a JWT strategy using Passport. Create a file named jwt.strategy.ts
:
// jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'your_secret_key', // Should be environment variable
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
Creating the Authentication Controller
Now, let’s create a controller to handle user login requests. Generate the controller:
nest generate controller auth
In auth.controller.ts
, implement the login endpoint:
// auth.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
async login(@Body() user: any) {
return this.authService.login(user);
}
}
Securing Routes with JWT
To secure your routes, use the @UseGuards()
decorator provided by NestJS. Here’s an example of securing a protected route:
// app.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from './jwt-auth.guard'; // Implement this guard
@Controller('protected')
export class AppController {
@UseGuards(JwtAuthGuard)
@Get()
getProtectedResource() {
return { message: 'This is a protected resource' };
}
}
Testing Your Implementation
You can test your JWT authentication using tools like Postman. Here’s a quick guide:
-
Login: Send a POST request to
/auth/login
with JSON payload containing the username and password. You should receive a JWT in response. -
Access Protected Route: Use the received JWT as a Bearer token in the Authorization header to access the protected route.
Conclusion
Implementing JWT authentication in a NestJS API with TypeScript can greatly enhance your application's security. By following the steps outlined in this article, you now have a solid foundation to build upon. Always remember to manage your secrets securely and to handle user data responsibly. Happy coding!