NestJS JWT Authentication
JWT (JSON Web Token) is the most widely used method for authenticating API requests. When a user logs in, the server generates a signed token and sends it to the client. The client attaches that token to every subsequent request. The server verifies the token's signature without touching the database — making JWT authentication fast and stateless.
How JWT Works
Step 1: User logs in
Client → POST /auth/login { email, password }
|
v
Server validates credentials
|
v
Server generates JWT token
|
v
Response: { access_token: "eyJhb..." }
Step 2: User accesses protected route
Client → GET /profile
Authorization: Bearer eyJhb...
|
v
Guard verifies the token signature
|
v
Token valid → route handler runs
Token invalid → 401 Unauthorized
JWT Token Structure
A JWT has three base64-encoded parts separated by dots:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlhdCI6MTY...}
Header.Payload.Signature
Header: { "alg": "HS256", "typ": "JWT" }
Payload: { "sub": 1, "email": "alice@example.com", "iat": 1705000000, "exp": 1705003600 }
Signature: HMACSHA256(base64(header) + "." + base64(payload), SECRET_KEY)
The payload carries the user's ID and any other claims (roles, email). The signature ensures the payload was not tampered with. Anyone can read the payload (it is only encoded, not encrypted), so never store passwords or sensitive secrets inside a JWT.
Installing Required Packages
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt npm install -D @types/passport-jwt @types/bcrypt
Auth Module Setup
// auth.module.ts
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
UsersModule,
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1h' },
}),
],
controllers: [AuthController],
providers: [AuthService],
exports: [AuthService],
})
export class AuthModule {}
Auth Service — Login and Token Generation
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
) {}
async validateUser(email: string, password: string): Promise<any> {
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: any) {
const payload = { sub: user.id, email: user.email };
return {
access_token: this.jwtService.sign(payload),
};
}
}
Auth Controller — Login Endpoint
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
async login(@Body() loginDto: LoginDto) {
const user = await this.authService.validateUser(
loginDto.email,
loginDto.password,
);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
return this.authService.login(user);
}
@Post('register')
register(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
}
JWT Guard — Protecting Routes
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
This guard uses Passport's JWT strategy (configured separately) to verify the token on every protected route. Apply it to any route or controller that requires authentication:
@Controller('users')
export class UsersController {
@Get('profile')
@UseGuards(JwtAuthGuard)
getProfile(@Req() req) {
return req.user; // populated by the JWT strategy after verification
}
@Get() // no guard — public route
findAll() {
return this.usersService.findAll();
}
}
JWT Strategy
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: any) {
return { id: payload.sub, email: payload.email };
// This object is attached to req.user
}
}
Token Expiry and Security
Best practices for JWT security: - Set a short expiry on access tokens (15 minutes to 1 hour) - Store the JWT_SECRET in an environment variable, never in code - Use refresh tokens for long-lived sessions (covered in Topic 34) - Use HTTPS to prevent token interception in transit - Never store sensitive data (passwords, credit cards) in the payload
JWT authentication keeps your server stateless — no session data stored in a database or memory. Every request carries its own proof of identity in the token. This scales effortlessly across multiple servers because any server can verify any token using the same secret key, with no coordination required.
