Rate Limiting Implementation
This document outlines the rate limiting strategy implemented for the SparkyFitness application, focusing on protecting sensitive authentication endpoints.
Purpose
Rate limiting is crucial for:
- Preventing Brute-Force Attacks: Limiting the number of login attempts from a single IP address within a given time frame.
- Mitigating Denial-of-Service (DoS) Attacks: Restricting the rate of requests to prevent server overload.
- Preventing Account Creation Spam: Limiting the rate of new user registrations.
- Protecting Password Reset Flows: Preventing abuse of email-based recovery mechanisms.
- Securing MFA Endpoints: Limiting attempts to bypass multi-factor authentication.
Implementation Layer
Rate limiting is implemented at the Nginx reverse proxy layer. This is the most efficient approach as it blocks malicious requests before they reach the backend Node.js application, conserving server resources.
Nginx Configuration
The rate limiting is configured in the nginx.conf file.
1. Client IP Detection
To correctly identify clients behind proxies or CDNs like Cloudflare, the configuration uses a chain of IP detection mechanisms:
# Trust docker gateway and Cloudflare IPs for real IP detection
set_real_ip_from 172.16.0.0/12; # Docker networks
set_real_ip_from 10.0.0.0/8; # Private networks
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
map $http_cf_connecting_ip $client_ip_from_cf {
"" $binary_remote_addr;
default $http_cf_connecting_ip;
}
map $http_x_real_ip $rate_limit_key {
"" $client_ip_from_cf;
default $http_x_real_ip;
}
The detection priority is:
CF-Connecting-IPheader (Cloudflare)X-Real-IPheader (other proxies)- Direct connection IP (
$binary_remote_addr)
2. Defining a Rate Limiting Zone
A shared memory zone named auth_zone is defined to track request rates based on client IP addresses:
limit_req_zone $rate_limit_key zone=auth_zone:10m rate=${NGINX_RATE_LIMIT};
limit_req_status 429;
$rate_limit_key: Uses the detected client IP address for tracking.zone=auth_zone:10m: Defines a 10-megabyte shared memory zone.rate=${NGINX_RATE_LIMIT}: Configurable rate limit (default:5r/s). Set to10000r/sto effectively disable rate limiting.limit_req_status 429: Explicitly returns HTTP 429 (Too Many Requests) when rate limited.
3. Applying the Rate Limit to Endpoints
The limit_req directive is applied to specific authentication endpoints within the server block.
Exact match endpoints:
# Apply rate limit to login endpoint
location = /api/auth/login {
limit_req zone=auth_zone burst=5 nodelay;
proxy_pass http://${SPARKY_FITNESS_SERVER_HOST}:${SPARKY_FITNESS_SERVER_PORT}/auth/login;
# ... other proxy settings ...
}
# Apply rate limit to register endpoint
location = /api/auth/register {
limit_req zone=auth_zone burst=5 nodelay;
proxy_pass http://${SPARKY_FITNESS_SERVER_HOST}:${SPARKY_FITNESS_SERVER_PORT}/auth/register;
# ... other proxy settings ...
}
Regex match for MFA endpoints:
# Apply rate limit to all MFA endpoints
location ~ ^/api/auth/mfa(/.*)?$ {
limit_req zone=auth_zone burst=5 nodelay;
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://${SPARKY_FITNESS_SERVER_HOST}:${SPARKY_FITNESS_SERVER_PORT};
# ... other proxy settings ...
}
Configuration parameters:
limit_req zone=auth_zone: Refers to the defined rate limiting zone.burst=5: Allows a burst of up to 5 requests beyond the defined rate.nodelay: Requests exceeding the burst limit are immediately rejected with a429 Too Many Requestserror, rather than being delayed.
Endpoints Protected
The following authentication endpoints are protected by Nginx rate limiting:
| Endpoint | Purpose |
|---|---|
/api/auth/login | User login |
/api/auth/register | New user registration |
/api/auth/forgot-password | Password reset request |
/api/auth/reset-password | Password reset completion |
/api/auth/request-magic-link | Magic link request |
/api/auth/magic-link-login | Magic link authentication |
/api/auth/mfa/* | All MFA-related endpoints |
Configuration
The rate limit can be configured via the NGINX_RATE_LIMIT environment variable in your Docker Compose configuration:
environment:
- NGINX_RATE_LIMIT=5r/s # Default: 5 requests per second
To effectively disable rate limiting (e.g., for testing), set a very high value:
environment:
- NGINX_RATE_LIMIT=10000r/s
Testing the Rate Limiting
To test the rate limiting, ensure your Docker Compose environment is running with the updated nginx.conf. Then, use curl to send a high volume of requests to the protected endpoints.
Example curl command (replace with your domain and adjust payload):
for i in $(seq 1 15); do curl -k -s -o /dev/null -w "%{http_code}\n" -X POST -H "Content-Type: application/json" -d '{"email":"test@example.com", "password":"password"}' https://your-domain.com/api/auth/login & done
You should observe 429 Too Many Requests HTTP status codes for requests exceeding the defined rate and burst limits.
Note on 503 Errors During Testing
During initial testing, 503 Service Unavailable errors might be observed instead of 429s. This indicates that Nginx is indeed applying the rate limit (as confirmed by Nginx error logs showing limiting requests), but it's also encountering issues connecting to or receiving timely responses from the backend server. While the rate limiting itself is functional, consistent 503s suggest an underlying issue with the backend's stability or readiness under load, which is outside the scope of the rate limiting implementation itself.
