Skip to Content
How It Works?Rate Limiting

Rate Limiting

The backend uses IP-based rate limiting to protect sensitive endpoints from brute force attacks and abuse. It is built with Bucket4j (token bucket algorithm) and Redis for distributed bucket storage.

How It Works

A custom @RateLimit annotation is applied to controller methods that need throttling:

@RateLimit(requests = 10, duration = 60) @PostMapping("/authenticate") public ResponseEntity<Void> authenticate(...) { ... }
  • requests — maximum number of requests allowed in the window.
  • duration — window size in seconds.

When a request comes in, the RateLimitInterceptor checks if the handler method has a @RateLimit annotation. If it does, a token bucket is resolved for the client’s IP and endpoint combination. If the bucket is exhausted, a 429 Too Many Requests response is returned with a Retry-After header.

Client IP Resolution

The client IP is resolved via HttpRequestUtil.resolveClientIp(). It checks the configured server.client-ip-header first (a CDN-specific header such as CF-Connecting-IP for Cloudflare), falling back to request.getRemoteAddr(). In production, Tomcat’s RemoteIpValve (server.forward-headers-strategy: native) ensures getRemoteAddr() reflects the real client IP from X-Forwarded-For set by Traefik. See the Deploy page for configuration details.

Rate-Limited Endpoints

EndpointLimit
POST /auth/authenticate10 req / 60s
POST /auth/register5 req / 60s
POST /auth/tokens/refresh10 req / 60s
POST /auth/password/forgot3 req / 60s
POST /auth/password/reset5 req / 60s
POST /auth/verification-email3 req / 60s
POST /auth/verify-email5 req / 60s
POST /users/username/availability10 req / 60s
POST /users/email/availability10 req / 60s
PATCH /profile/password5 req / 60s

Bucket Isolation

Each rate limit bucket is scoped to a specific endpoint + client IP combination. This means:

  • Different endpoints have independent limits — exhausting the login limit does not affect forgot-password.
  • Different IPs have independent limits — one client being throttled does not affect others.

Configuration

Rate limiting is enabled by default. It can be disabled by setting rate-limit.enabled to false in application.yml. This is used in the test profile to prevent interference with integration tests.

Storage

Buckets are stored in Redis via Bucket4j’s Lettuce integration. Redis keys are namespaced as rate-limit:<Controller>:<method>:<ip> and expire automatically using a basedOnTimeForRefillingBucketUpToMax strategy — keys live only as long as the longest configured rate limit window. Since buckets are stored in the same Redis instance used for token storage, no additional infrastructure is required. This also makes rate limiting work correctly across multiple API instances.

Last updated on