typescript
42 lines · 8 steps
Rate-limiting an auth flow in NestJS
A NestJS auth controller applies per-endpoint throttling to defend login, 2FA, and token refresh against abuse.
Explained by
highlit
1import { Body, Controller, Ip, Post, UnauthorizedException } from '@nestjs/common';
2import { Throttle, ThrottlerGuard } from '@nestjs/throttler';
3import { UseGuards } from '@nestjs/common';
4import { AuthService } from './auth.service';
5import { LoginDto } from './dto/login.dto';
6
7@Controller('auth')
8@UseGuards(ThrottlerGuard)
9export class AuthController {
10 constructor(private readonly authService: AuthService) {}
11
12 @Post('login')
13 @Throttle({ default: { limit: 5, ttl: 60_000 } })
14 async login(@Body() dto: LoginDto, @Ip() ip: string) {
15 const user = await this.authService.validateCredentials(dto.email, dto.password);
16
17 if (!user) {
18 await this.authService.recordFailedAttempt(dto.email, ip);
19 throw new UnauthorizedException('Invalid email or password');
20 }
21
22 return this.authService.issueTokens(user, ip);
23 }
24
25 @Post('login/verify-2fa')
26 @Throttle({ default: { limit: 3, ttl: 300_000 } })
27 async verifyTwoFactor(@Body() dto: { challengeId: string; code: string }, @Ip() ip: string) {
28 const session = await this.authService.verifyOtp(dto.challengeId, dto.code, ip);
29
30 if (!session) {
31 throw new UnauthorizedException('Invalid or expired verification code');
32 }
33
34 return session;
35 }
36
37 @Post('token/refresh')
38 @Throttle({ default: { limit: 30, ttl: 60_000 } })
39 async refresh(@Body('refreshToken') refreshToken: string, @Ip() ip: string) {
40 return this.authService.rotateRefreshToken(refreshToken, ip);
41 }
42}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Applying a guard at the class level covers every route while per-route decorators fine-tune the policy for each one.
- 2Different auth actions deserve different rate limits based on their risk and expected call frequency.
- 3Uniform error messages and recorded failed attempts limit what an attacker learns from probing credentials.
Related explainers
typescript
import { useState, useEffect, useRef, useCallback } from "react"; interface Suggestion { id: string;
A debounced autocomplete hook in React
debounce
custom-hooks
abortcontroller
Advanced
7 steps
typescript
import { Component, computed, DestroyRef, inject, input, output, signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { interval, map, takeWhile } from 'rxjs';
How a signal-driven countdown works in Angular
signals
reactivity
rxjs
Intermediate
8 steps
typescript
type TokenType = "keyword" | "string" | "comment" | "number" | "text"; interface Token { type: TokenType;
How a regex tokenizer highlights code
tokenizer
regex
lexing
Intermediate
10 steps
php
<?php namespace App\Services;
How user impersonation works in Laravel
authentication
authorization
session
Intermediate
8 steps
python
import secrets from fastapi import Depends, FastAPI, HTTPException, Security, status from fastapi.security import APIKeyHeader
API key authentication as a FastAPI dependency
authentication
dependency-injection
api-keys
Intermediate
8 steps
typescript
import { Component, inject, signal } from '@angular/core'; import { CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop'; import { HttpClient } from '@angular/common/http'; import { finalize } from 'rxjs';
Drag-and-drop reordering with signals in Angular
drag-and-drop
signals
optimistic-update
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/rate-limiting-an-auth-flow-in-nestjs-explained-typescript-d4f3/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.