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.

Leave a Comment

Your email address will not be published. Required fields are marked *