Authentication is a critical aspect of modern web applications, and NestJS makes it simpler to implement robust authentication mechanisms using Passport. Whether you're building a REST API or a traditional web application, integrating authentication into your project is essential for securing user data and managing access control.
What is Passport?
Passport is a middleware for Node.js that simplifies the implementation of various authentication strategies. From simple username/password to OAuth, it provides a wide array of options to fit various use cases. When used with NestJS, it allows for clean and organized authentication practices consistent with the framework's philosophy.
Setting Up NestJS with Passport
To start, ensure you have a NestJS project set up. If you haven't set up one yet, simply create a new Nest application:
$ npm i -g @nestjs/cli $ nest new nest-auth-example $ cd nest-auth-example
Installing Dependencies
Next, install the necessary packages for Passport and JWT (JSON Web Tokens):
$ npm install @nestjs/passport passport passport-local @nestjs/jwt passport-jwt bcrypt
Here's a quick rundown of what each package does:
@nestjs/passport
: A NestJS module for integrating Passport.passport
: The core Passport library.passport-local
: This strategy allows username/password authentication.@nestjs/jwt
: JWT module for generating tokens.passport-jwt
: This strategy provides authentication using JWT.bcrypt
: A library to hash passwords for a secure authentication process.
Creating the User Module
Before we dive into authentication, we'll need to set up a user model that our authentication strategies will use. Create a user module by running:
$ nest generate module users $ nest generate service users
Create a simple user entity, such as:
// src/users/user.entity.ts import { Exclude } from 'class-transformer'; export class User { id: number; username: string; @Exclude() password: string; }
User Service Implementation
In the users.service.ts
, implement basic features to manage users:
// src/users/users.service.ts import { Injectable } from '@nestjs/common'; import { User } from './user.entity'; @Injectable() export class UsersService { private readonly users: User[] = []; async create(username: string, password: string): Promise<User> { const user = new User(); user.id = this.users.length + 1; user.username = username; user.password = await this.hashPassword(password); this.users.push(user); return user; } async findOne(username: string): Promise<User | undefined> { return this.users.find(user => user.username === username); } private async hashPassword(password: string): Promise<string> { return bcrypt.hash(password, 10); } }
Here, we have basic logic to create a user and find one based on the username.
Implementing the Local Strategy
Next, integrate the Passport Local strategy to handle user login:
// src/auth/local.strategy.ts import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-local'; import { UsersService } from '../users/users.service'; import { User } from '../users/user.entity'; @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private readonly usersService: UsersService) { super({ usernameField: 'username' }); } async validate(username: string, password: string): Promise<User> { const user = await this.usersService.findOne(username); if (!user || !(await bcrypt.compare(password, user.password))) { throw new UnauthorizedException(); } return user; } }
Handling JWT Authentication
Now, let's create JWT strategies for generating tokens:
- Create a new authentication module:
$ nest generate module auth
- Implement JWT strategy:
// src/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'; import { User } from '../users/user.entity'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor(private readonly usersService: UsersService) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: 'your_secret_key', // Change this to your actual secret }); } async validate(payload: any): Promise<User> { return this.usersService.findOne(payload.username); } }
- Create a login method in
auth.service.ts
to generate the JWT:
// src/auth/auth.service.ts 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): Promise<{ access_token: string }> { const user = await this.usersService.findOne(username); const payload = { username: user.username, sub: user.id }; return { access_token: this.jwtService.sign(payload), }; } }
Creating the Authentication Controller
Lastly, create an authentication controller to handle the incoming requests:
// src/auth/auth.controller.ts import { Controller, Post, Request, UseGuards } from '@nestjs/common'; import { AuthService } from './auth.service'; import { LocalAuthGuard } from './local-auth.guard'; // You need to create this guard @Controller('auth') export class AuthController { constructor(private authService: AuthService) {} @UseGuards(LocalAuthGuard) @Post('login') async login(@Request() req) { return this.authService.login(req.user); } }
Securing Routes with Guards
To protect your routes, you can use guards. For example, to use the JWT strategy:
// src/auth/jwt-auth.guard.ts import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {}
Now, you can secure any route by applying @UseGuards(JwtAuthGuard)
.
Testing the Authentication Flow
With the authentication flow set up, you can test it via Postman or any other API client:
-
Register a User: Make a POST request to your user registration endpoint (not implemented in detail here but can be derived from
UsersService
). -
Login: Send a POST request to
/auth/login
with the username and password. -
Access Protected Route: Use the received JWT token as a Bearer token to access protected routes.
This setup provides you with a solid foundation for incorporating authentication into your NestJS application. Feel free to enhance this system with additional features such as refresh tokens, social logins, etc. The power of NestJS combined with Passport's flexibility opens numerous avenues for building secure and scalable applications.