Authentication is a critical aspect of building secure applications. JSON Web Tokens (JWT) have become a de-facto standard for this purpose, offering a compact and self-contained way to represent authentication and authorization information. NestJS, a progressive Node.js framework for building efficient, reliable, and scalable server-side applications, seamlessly integrates with JWT. In this article, we’ll delve into how to implement JWT-based authentication in a NestJS application using TypeScript, ensuring your application is both secure and scalable.
Prerequisites
Before diving into the implementation, ensure you have the following:
- Basic knowledge of NestJS and TypeScript
- Node.js installed on your machine
- NestJS CLI installed globally
Getting Started
Let’s start by setting up a new NestJS project:
$ nest new jwt-authentication
$ cd jwt-authentication
Install the necessary dependencies for JWT and Passport.js (authentication middleware):
$ npm install @nestjs/jwt @nestjs/passport passport passport-jwt
Setting Up Modules
We will create an AuthModule
to encapsulate all authentication-related logic. We'll also need a UsersModule
to manage user-related data.
Auth Module
Create the files necessary for the AuthModule
:
$ nest g module auth
$ nest g service auth
$ nest g controller auth
Users Module
Similarly, create the files necessary for the UsersModule
:
$ nest g module users
$ nest g service users
$ nest g controller users
Implementing JWT Strategy
Create a new file jwt.strategy.ts
under the src/auth
directory:
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { jwtConstants } from './constants';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
We’ll define jwtConstants
in a new file constants.ts
under src/auth
:
export const jwtConstants = {
secret: 'yourSecretKey', // Replace with a strong secret
};
AuthService Implementation
Edit the src/auth/auth.service.ts
to include JWT token generation and validation logic:
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 validateUser(username: string, pass: string): Promise<any> {
const user = await this.usersService.findOne(username);
if (user && user.password === pass) {
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),
};
}
}
UsersService Implementation
For simplicity, we’ll use an in-memory store for user data. Adjust src/users/users.service.ts
:
import { Injectable } from '@nestjs/common';
export type User = any;
@Injectable()
export class UsersService {
private readonly users: User[] = [
{
userId: 1,
username: 'john',
password: 'changeme',
},
{
userId: 2,
username: 'chris',
password: 'secret',
},
{
userId: 3,
username: 'maria',
password: 'guess',
},
];
async findOne(username: string): Promise<User | undefined> {
return this.users.find(user => user.username === username);
}
}
Wiring Everything Together
Edit src/auth/auth.module.ts
to integrate all the parts:
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '60m' },
}),
],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
Also, edit src/users/users.module.ts
:
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
AuthController Implementation
Edit src/auth/auth.controller.ts
to handle login requests:
import { Controller, Request, Post, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LocalAuthGuard } from './local-auth.guard';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@UseGuards(LocalAuthGuard)
@Post('login')
async login(@Request() req) {
return this.authService.login(req.user);
}
}
LocalAuthGuard Implementation
Create a local-auth.guard.ts
in src/auth
:
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext) {
const result = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return result;
}
}
Protecting Routes
To protect routes, simply use the @UseGuards(AuthGuard('jwt'))
decorator:
Example:
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller('profile')
export class ProfileController {
@UseGuards(AuthGuard('jwt'))
@Get()
getProfile(@Request() req) {
return req.user;
}
}
Conclusion
JWT is a robust solution for authentication in modern applications. By leveraging NestJS and Passport.js, you can create a scalable and secure authentication system. This article provides a basic implementation to get you started. Ensure to enhance security by using strong secret keys, proper error handling, and secure storage for sensitive information.
Happy coding! 🚀
Note: Always remember to never hard-code your secrets in the source code. Use environment variables or a configuration service to manage sensitive information securely.
Feel free to leave comments and feedback, and share your experiences with implementing JWT in your NestJS applications!