How to implement authentication with Passport in NestJS?
Authentication in NestJS with Passport
NestJS integrates seamlessly with Passport.js — the most popular Node.js authentication library — through the @nestjs/passport package.
Setup
bash
npm install @nestjs/passport passport passport-local passport-jwt
npm install @nestjs/jwt
npm install --save-dev @types/passport-local @types/passport-jwtJWT Strategy (Most Common for APIs)
1. Auth Module
typescript
@Module({
imports: [
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1h' },
}),
UsersModule,
],
providers: [AuthService, JwtStrategy, LocalStrategy],
controllers: [AuthController],
})
export class AuthModule {}2. Local Strategy (Login)
typescript
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'email' });
}
async validate(email: string, password: string): Promise<User> {
const user = await this.authService.validateUser(email, password);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
return user;
}
}3. JWT Strategy (Protect Routes)
typescript
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: { sub: string; email: string }) {
return { id: payload.sub, email: payload.email };
}
}4. Auth Service
typescript
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(email: string, password: string) {
const user = await this.usersService.findByEmail(email);
if (user && await bcrypt.compare(password, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: User) {
const payload = { sub: user.id, email: user.email };
return {
access_token: this.jwtService.sign(payload),
refresh_token: this.jwtService.sign(payload, { expiresIn: '7d' }),
};
}
async refreshToken(token: string) {
const payload = this.jwtService.verify(token);
const newPayload = { sub: payload.sub, email: payload.email };
return {
access_token: this.jwtService.sign(newPayload),
};
}
}5. Auth Controller
typescript
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@UseGuards(AuthGuard('local'))
@Post('login')
async login(@Request() req) {
return this.authService.login(req.user);
}
@UseGuards(AuthGuard('jwt'))
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
@Post('refresh')
async refresh(@Body('refresh_token') token: string) {
return this.authService.refreshToken(token);
}
}Global JWT Guard
typescript
// jwt-auth.guard.ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
return super.canActivate(context);
}
}typescript
// app.module.ts — apply globally
@Module({
providers: [
{ provide: APP_GUARD, useClass: JwtAuthGuard },
],
})
export class AppModule {}Now use @Public() decorator to skip auth:
typescript
@Controller('health')
export class HealthController {
@Public()
@Get()
check() {
return { status: 'ok' };
}
}Flow Summary
Login: POST /auth/login
→ LocalStrategy.validate() → AuthService.login() → JWT token
Request: GET /api/protected
Header: Authorization: Bearer <token>
→ JwtStrategy.validate() → req.user populated → ControllerBest practice: Use JWT for stateless API authentication. Implement refresh tokens with rotation. Store tokens in httpOnly cookies for web apps. Apply
JwtAuthGuardglobally and use@Public()for open routes.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.