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.