Skip to content

Add rate limiting on login and signup to prevent brute-force attacks#381

Merged
mehul-m-prajapati merged 1 commit into
GitMetricsLab:mainfrom
advikdivekar:security/rate-limit-auth-endpoints
May 23, 2026
Merged

Add rate limiting on login and signup to prevent brute-force attacks#381
mehul-m-prajapati merged 1 commit into
GitMetricsLab:mainfrom
advikdivekar:security/rate-limit-auth-endpoints

Conversation

@advikdivekar
Copy link
Copy Markdown
Contributor

What is the problem

/api/auth/login and /api/auth/signup have no request throttling. An attacker can issue unlimited automated requests against either endpoint:

  • Credential stuffing / brute-force on login: with the 10M-entry rockyou wordlist and no server-side resistance, a common or weak password is cracked in minutes.
  • Mass account registration on signup: bots can register thousands of fake accounts with no cost, exhausting MongoDB storage and polluting the user base.

Neither endpoint has any lockout, delay, CAPTCHA, or IP-based throttle.

What was changed

backend/server.js

  • Added const rateLimit = require('express-rate-limit') to the imports.
  • Defined authLimiter: 10 requests per 15-minute window per IP, with standardHeaders: true (sets RateLimit-* headers per RFC 6585), legacyHeaders: false, a clear JSON error message, and skipSuccessfulRequests: true so only failed attempts count against the quota.
  • Applied authLimiter to /api/auth/login and /api/auth/signup individually via app.use(), mounted after passport.session() and before the route handlers so it cannot be bypassed by any route-level code path.

backend/package.json

  • Added express-rate-limit: ^7.5.1 as a production dependency.

Why this approach

skipSuccessfulRequests: true is essential for usability: a legitimate user who provides correct credentials on their first attempt does not consume quota and is never blocked. Only failed attempts deplete the window. This makes the limiter aggressive against automated attackers (who almost always fail) while being invisible to legitimate users.

Mounting the limiter at the app.use('/api/auth/login', authLimiter) level, rather than inside the route handler, means it applies before any authentication logic runs — the rate limit check is the very first thing that executes for these paths.

The limiter block is placed after passport.session() and before // Routes — a region with completely unique surrounding context that does not overlap with any other pending changes to server.js, ensuring clean merges.

How to test

  1. Send 11 consecutive POST /api/auth/login requests with wrong credentials from the same IP:
    for i in $(seq 1 11); do curl -s -X POST http://localhost:5000/api/auth/login \
      -H "Content-Type: application/json" \
      -d '{"email":"test@example.com","password":"wrong"}' ; done
    The 11th response must be HTTP 429 with body { "message": "Too many attempts, please try again after 15 minutes." }.
  2. Send a successful login on attempt 3. Confirm the counter resets — the next 10 failed attempts are again permitted before the 429 fires.
  3. Confirm RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers appear on every response from these endpoints.
  4. Confirm a request from a different IP is not affected by another IP's quota.

Edge cases covered

  • skipSuccessfulRequests: true prevents legitimate users from being locked out when they authenticate correctly.
  • standardHeaders: true lets well-behaved clients read the RateLimit-* headers and back off before hitting the 429.
  • The limiter is applied to both login and signup independently so neither endpoint can be used to exhaust the other's quota.
  • legacyHeaders: false suppresses the deprecated X-RateLimit-* headers to keep responses clean.

Verification

  • Root cause fully resolved
  • All edge cases handled
  • No regressions — successful auth flows are unaffected; the in-memory default store resets between server restarts (acceptable for the scope of this fix)
  • Only the imports block and the post-passport-session block in server.js are modified; the CORS block and session config are untouched
  • Code matches project style and conventions

Labels: type:security level:intermediate gssoc:approved

Closes #372

Please assign this PR to me under GSSoC 2026.

/api/auth/login and /api/auth/signup had no request throttling,
allowing unlimited automated password guessing with no server-side
resistance.

Add express-rate-limit with a 10-request per 15-minute per-IP window
on both endpoints. skipSuccessfulRequests:true ensures only failed
attempts count against the quota, so legitimate users who succeed
on the first try are not penalised.

The limiter is mounted after passport.session() but before the route
handlers so it cannot be bypassed by any route-level code.

Add express-rate-limit ^7.5.1 to package.json dependencies.

Closes GitMetricsLab#372
@netlify
Copy link
Copy Markdown

netlify Bot commented May 21, 2026

Deploy Preview for github-spy ready!

Name Link
🔨 Latest commit 3994988
🔍 Latest deploy log https://app.netlify.com/projects/github-spy/deploys/6a0f5f4afab9e90008b27cb3
😎 Deploy Preview https://deploy-preview-381--github-spy.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Warning

Rate limit exceeded

@advikdivekar has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 22 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77c998a0-9d1c-4a42-b8a6-9f92cbf21da1

📥 Commits

Reviewing files that changed from the base of the PR and between 9d34c19 and 3994988.

📒 Files selected for processing (2)
  • backend/package.json
  • backend/server.js
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mehul-m-prajapati mehul-m-prajapati merged commit e9f9108 into GitMetricsLab:main May 23, 2026
7 checks passed
@github-actions
Copy link
Copy Markdown

🎉🎉 Thank you for your contribution! Your PR #381 has been merged! 🎉🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] No Rate Limiting on /api/auth/login and /api/auth/signup — Brute-Force Attack Vector

2 participants