NestJS — Enterprise Node.js

Authentication with Passport

17 min Lesson 26 of 30

Authentication with Passport

Authentication answers "who are you?" — verifying a user's identity. NestJS integrates Passport, the de-facto authentication library for Node.js, through @nestjs/passport. Passport organises authentication into strategies, and we start with the most fundamental: verifying an email and password.

Authentication vs authorization

Two different questions. Authentication confirms identity (who you are — login). Authorization decides permissions (what you may do — roles). This phase covers both, starting with authentication. Do not conflate them.

The pieces

  • Passport strategy — the rule for verifying a credential (local, JWT, OAuth…).
  • AuthGuard — a guard that runs a strategy on a route.
  • AuthService — your code that checks the user against the database.

Setting up

npm install @nestjs/passport passport passport-local npm install -D @types/passport-local

The validation logic

Your AuthService contains the actual check: find the user and compare the password (hashed, never plain — using bcrypt or argon2):

import { Injectable, UnauthorizedException } from '@nestjs/common'; import * as bcrypt from 'bcrypt'; @Injectable() export class AuthService { constructor(private users: UsersService) {} async validateUser(email: string, password: string) { const user = await this.users.findByEmail(email); if (user && (await bcrypt.compare(password, user.passwordHash))) { const { passwordHash, ...safe } = user; return safe; // never return the hash } return null; // invalid credentials } }

The local strategy

The strategy wires Passport to your validation. validate() is called with the credentials; returning a user attaches it to the request, while throwing rejects the login:

import { Strategy } from 'passport-local'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable, UnauthorizedException } from '@nestjs/common'; @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super({ usernameField: 'email' }); // default field is 'username' } async validate(email: string, password: string) { const user = await this.authService.validateUser(email, password); if (!user) throw new UnauthorizedException(); return user; // becomes request.user } }

Protecting the login route

Apply AuthGuard('local') to the login handler. Passport runs the strategy first; if it succeeds, request.user is populated:

import { Controller, Post, UseGuards, Request } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Controller('auth') export class AuthController { @UseGuards(AuthGuard('local')) @Post('login') login(@Request() req) { return req.user; // strategy already validated and attached the user } }
Always hash passwords; never store or compare them in plain text. Use bcrypt or argon2, compare with the library's constant-time function, and never return the hash in an API response. Plain-text passwords are an unforgivable security failure.
Reuse guards from earlier. AuthGuard is just a NestJS guard backed by Passport — the same CanActivate concept from Phase 3. The strategy supplies the verification logic; the guard runs it.

Summary

Passport (via @nestjs/passport) structures authentication into strategies. The local strategy verifies email + password: your AuthService.validateUser() checks a hashed password, the LocalStrategy.validate() calls it, and AuthGuard('local') protects the login route, attaching the user to the request. Always hash passwords. Once a user is verified, you need a token to keep them logged in — that is JWT, next.