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:

  1. CF-Connecting-IP header (Cloudflare)
  2. X-Real-IP header (other proxies)
  3. 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 to 10000r/s to 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 a 429 Too Many Requests error, rather than being delayed.

Endpoints Protected

The following authentication endpoints are protected by Nginx rate limiting:

EndpointPurpose
/api/auth/loginUser login
/api/auth/registerNew user registration
/api/auth/forgot-passwordPassword reset request
/api/auth/reset-passwordPassword reset completion
/api/auth/request-magic-linkMagic link request
/api/auth/magic-link-loginMagic 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.