NestJS Passport.js
Passport.js is the most widely used authentication middleware for Node.js. It supports over 500 authentication strategies — JWT, Google OAuth, GitHub, Facebook, local username/password, and more. NestJS integrates Passport through the @nestjs/passport package, which wraps each strategy into a clean, injectable service that works with NestJS's dependency injection and guard system.
How Passport Works in NestJS
Client Request with credentials
|
v
Passport Guard triggers the strategy
|
v
Strategy extracts credentials from request
(token from header, username/password from body, etc.)
|
v
Strategy's validate() method runs
(verify token, check password, look up user)
|
v
Validated user attached to req.user
|
v
Route handler executes
The Two Main Strategies
Local Strategy — Username and Password
The local strategy handles traditional login with email and password. It reads the credentials from the request body and calls your validate method:
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'email' }); // use 'email' instead of 'username'
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authService.validateUser(email, password);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
return user; // attached to req.user
}
}
JWT Strategy — Token-Based Auth
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: { sub: number; email: string }) {
return { id: payload.sub, email: payload.email };
}
}
Registering Strategies
Each strategy is a provider. Register it in the auth module:
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({ secret: process.env.JWT_SECRET, signOptions: { expiresIn: '1h' } }),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
controllers: [AuthController],
})
export class AuthModule {}
Creating Guards for Each Strategy
// local.guard.ts — used on the login route
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
// jwt.guard.ts — used on protected routes
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
The string argument ('local', 'jwt') identifies which Passport strategy the guard uses. When the guard activates, Passport finds the matching registered strategy and runs it.
Auth Flow Diagram
Login Flow:
POST /auth/login { email, password }
→ LocalAuthGuard triggers LocalStrategy
→ LocalStrategy.validate(email, password) runs
→ User returned → req.user set
→ AuthController.login(req.user) returns JWT token
Protected Route Flow:
GET /profile Authorization: Bearer <token>
→ JwtAuthGuard triggers JwtStrategy
→ JwtStrategy reads token from header
→ JwtStrategy.validate(payload) runs
→ { id, email } returned → req.user set
→ ProfileController.getProfile(req.user) runs
Google OAuth Strategy
Passport's real power shows with third-party OAuth strategies. Adding Google login requires the passport-google-oauth20 package:
npm install passport-google-oauth20 @types/passport-google-oauth20
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor() {
super({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'http://localhost:3000/auth/google/callback',
scope: ['email', 'profile'],
});
}
async validate(accessToken: string, refreshToken: string, profile: any) {
const { name, emails } = profile;
return {
email: emails[0].value,
name: `${name.givenName} ${name.familyName}`,
};
}
}
// Routes
@Get('google')
@UseGuards(AuthGuard('google'))
googleAuth() {} // redirects to Google login page
@Get('google/callback')
@UseGuards(AuthGuard('google'))
googleCallback(@Req() req) {
return this.authService.login(req.user);
}
Accessing the Current User in Controllers
@Get('profile')
@UseGuards(JwtAuthGuard)
getProfile(@Req() req: Request) {
return req.user; // populated by Passport after strategy runs
}
For cleaner code, create a custom @CurrentUser() decorator that extracts req.user automatically — eliminating the need to use @Req() and access the user manually from the request object in every controller method.
Why Passport in NestJS
Passport standardizes authentication across strategies. Adding a new login method — GitHub, Facebook, Apple — follows the same pattern: install the strategy package, create a strategy class, register it as a provider, create a guard. The rest of your application — guards, controllers, services — stays unchanged. Passport absorbs the complexity of each authentication protocol behind a consistent interface.
