Careeroza
Real World Example
Where You See This
- Banking apps (SBI, HDFC): card or netbanking blocked for 24 hours after 3 wrong PINs/passwords.
- GitHub, Twitter/X APIs:
X-RateLimit-Remainingand429withRetry-Afterheaders. - Flipkart / Amazon OTP: "You have exceeded the maximum OTP attempts. Try after 30 minutes."
ShopEase Login Defence in Layers
| Layer | Limit | On exceed |
|---|---|---|
| Cloudflare / gateway | 100 requests / minute per IP to /api/auth/** | 429 |
| LoginRateLimitFilter (Redis) | 10 login attempts / minute per IP | 429 + Retry-After |
| Per account | 3 failures | CAPTCHA required |
| Per account | 5 failures | locked 15 min, email "Was this you?" |
| OTP send | 3 SMS / 10 min per phone, 10 / day | 429, message to wait |
| Breached password check | on login and signup | force password reset |
@Bean
ProxyManager<String> redisProxyManager(RedisClient redisClient) {
StatefulRedisConnection<String, byte[]> connection =
redisClient.connect(RedisCodec.of(StringCodec.UTF8, ByteArrayCodec.INSTANCE));
return LettuceBasedProxyManager.builderFor(connection)
.withExpirationStrategy(ExpirationAfterWriteStrategy
.basedOnTimeForRefillingBucketUpToMax(Duration.ofMinutes(5)))
.build();
}
// In the filter: one shared bucket per IP across every ShopEase instance
Bucket bucket = proxyManager.builder().build("login:" + clientIp, () -> BucketConfiguration.builder()
.addLimit(limit -> limit.capacity(10).refillGreedy(10, Duration.ofMinutes(1)))
.build());Log output
Security log when a credential-stuffing bot hits ShopEase:
grep -E "RATE_LIMITED|ACCOUNT_LOCKED" logs/security.log | tail -3Example output
2026-03-01T02:14:09.310+05:30 WARN [shopease] RATE_LIMITED ip=185.220.101.47 path=/api/auth/login retryAfter=6s 2026-03-01T02:14:09.512+05:30 WARN [shopease] RATE_LIMITED ip=185.220.101.47 path=/api/auth/login retryAfter=6s 2026-03-01T02:16:44.902+05:30 WARN [shopease] ACCOUNT_LOCKED user=r***@gmail.com failures=5 lockedUntil=2026-03-01T02:31:44+05:30