Lesson 21 of 49 intermediate

Authentication — JWT & Guards

Who Are You? Prove It!

Open interactive version (quiz + challenge)

Real-world analogy

Authentication is like a nightclub. The bouncer (Guard) checks your ID (JWT token) at the door. If valid, you're in! If expired or fake, you're out. Some VIP rooms (admin routes) need a special stamp (role) on your wristband!

What is it?

Authentication verifies WHO you are (login). Authorization verifies WHAT you can do (permissions). JWTs are stateless tokens that contain encoded user info, verified by the server using a secret key.

Real-world relevance

Nearly every web app uses JWT for authentication. Google, GitHub, and Stripe all use OAuth + JWT. NestJS makes it clean with decorators like @UseGuards() and @Roles().

Key points

Code example

// 1. Login — get your JWT token 🎟️
@Controller('auth')
export class AuthController {
  constructor(
    private authService: AuthService,
    private jwtService: JwtService,
  ) {}

  @Post('login')
  async login(@Body() dto: LoginDto) {
    const user = await this.authService.validateUser(
      dto.email, dto.password
    );

    if (!user) {
      throw new UnauthorizedException('Invalid credentials');
    }

    // Create JWT with user info
    const token = this.jwtService.sign({
      sub: user.id,
      email: user.email,
      role: user.role, // 'admin', 'user', etc.
    });

    return { access_token: token };
  }
}

// 2. JWT Guard — the bouncer 🚪
@Injectable()
export class JwtGuard extends AuthGuard('jwt') {
  // Passport automatically verifies the token!
}

// 3. Role Guard — VIP check 🌟
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.get<string[]>(
      'roles', context.getHandler()
    );
    if (!requiredRoles) return true; // No roles required
    const { user } = context.switchToHttp().getRequest();
    if (!user) return false; // No authenticated user
    return requiredRoles.includes(user.role);
  }
}

// 4. Using guards on routes — clean & simple! ✨
@Controller('admin')
@UseGuards(JwtGuard, RolesGuard)  // Must be logged in + right role
export class AdminController {
  @Get('dashboard')
  @Roles('admin')  // Only admins!
  getDashboard() {
    return { message: 'Welcome to admin panel!' };
  }

  @Get('stats')
  @Roles('admin', 'manager')  // Admins OR managers
  getStats() {
    return this.statsService.getAll();
  }
}

Line-by-line walkthrough

  1. 1. 1. Login — get your JWT token 🎟️
  2. 2. Decorator that adds metadata or behavior
  3. 3. Exporting for use in other files
  4. 4.
  5. 5.
  6. 6.
  7. 7.
  8. 8.
  9. 9. Decorator that adds metadata or behavior
  10. 10.
  11. 11. Declaring a variable
  12. 12.
  13. 13. Closing expression
  14. 14.
  15. 15. Conditional check
  16. 16. Throwing an error
  17. 17. Closing block
  18. 18.
  19. 19. Create JWT with user info
  20. 20. Declaring a variable
  21. 21.
  22. 22.
  23. 23.
  24. 24.
  25. 25.
  26. 26. Returning a value
  27. 27. Closing block
  28. 28. Closing block
  29. 29.
  30. 30. 2. JWT Guard — the bouncer 🚪
  31. 31. Decorator that adds metadata or behavior
  32. 32. Exporting for use in other files
  33. 33. Passport automatically verifies the token!
  34. 34. Closing block
  35. 35.
  36. 36. 3. Role Guard — VIP check 🌟
  37. 37. Decorator that adds metadata or behavior
  38. 38. Exporting for use in other files
  39. 39.
  40. 40.
  41. 41.
  42. 42. Declaring a variable
  43. 43.
  44. 44. Closing expression
  45. 45. Declaring a variable
  46. 46. Returning a value
  47. 47. Closing block
  48. 48. Closing block
  49. 49.
  50. 50. 4. Using guards on routes — clean &amp; simple! ✨
  51. 51. Decorator that adds metadata or behavior
  52. 52. Decorator that adds metadata or behavior
  53. 53. Exporting for use in other files
  54. 54. Decorator that adds metadata or behavior
  55. 55. Decorator that adds metadata or behavior
  56. 56.
  57. 57. Returning a value
  58. 58. Closing block
  59. 59.
  60. 60. Decorator that adds metadata or behavior
  61. 61. Decorator that adds metadata or behavior
  62. 62.
  63. 63. Returning a value
  64. 64. Closing block
  65. 65. Closing block

Spot the bug

@Post('login')
async login(@Body() dto: LoginDto) {
  const user = await this.authService.validate(dto.email, dto.password);
  const token = this.jwtService.sign({
    sub: user.id,
    password: user.password,
  });
  return { access_token: token };
}
Need a hint?
JWT tokens are NOT encrypted - anyone can read them...
Show answer
The password is included in the JWT payload! JWTs are base64-encoded, not encrypted, so anyone can read them. Fix: remove 'password: user.password' from the token payload.

Explain like I'm 5

Authentication is like showing your library card to borrow books. First you sign up and get a card (token). Every time you want a book, you show the card. Some special books are only for teachers (admin roles). The librarian checks your card every time!

Fun fact

JWT tokens are NOT encrypted — they're just Base64 encoded. Anyone can read them! The 'signature' part just proves they weren't tampered with. Never put secrets in a JWT! 🤫

Hands-on challenge

Decode a JWT token at jwt.io — paste any token and see the three parts: Header, Payload, and Signature. Notice how the payload is readable!

More resources

Open interactive version (quiz + challenge) ← Back to course: Full-Stack Playbook