PhoenixKit.Users.RateLimiter (phoenix_kit v1.7.63)

Copy Markdown View Source

Rate limiting for authentication endpoints to prevent brute-force attacks.

This module provides comprehensive rate limiting protection for:

  • Login attempts (prevents password brute-forcing)
  • Magic link generation (prevents token enumeration)
  • Password reset requests (prevents mass reset attacks)
  • User registration (prevents spam account creation)

Configuration

Rate limits can be configured in your application config:

# config/config.exs
config :phoenix_kit, PhoenixKit.Users.RateLimiter,
  login_limit: 5,                    # Max login attempts per window
  login_window_ms: 60_000,           # 1 minute window
  magic_link_limit: 3,               # Max magic link requests per window
  magic_link_window_ms: 300_000,     # 5 minute window
  password_reset_limit: 3,           # Max password reset requests per window
  password_reset_window_ms: 300_000, # 5 minute window
  registration_limit: 3,             # Max registration attempts per window
  registration_window_ms: 3600_000,  # 1 hour window
  registration_ip_limit: 10,         # Max registrations per IP per window
  registration_ip_window_ms: 3600_000 # 1 hour window

Security Features

  • Email-based rate limiting: Prevents targeted attacks on specific accounts
  • IP-based rate limiting: Prevents distributed attacks from single sources
  • Timing attack mitigation: Consistent response times for valid/invalid emails
  • Exponential backoff: Automatically enforced through time windows
  • Comprehensive logging: All rate limit violations are logged for security monitoring

Usage Examples

# Check login rate limit
case PhoenixKit.Users.RateLimiter.check_login_rate_limit(email, ip_address) do
  :ok -> proceed_with_login()
  {:error, :rate_limit_exceeded} -> show_rate_limit_error()
end

# Check magic link rate limit
case PhoenixKit.Users.RateLimiter.check_magic_link_rate_limit(email) do
  :ok -> generate_magic_link()
  {:error, :rate_limit_exceeded} -> show_cooldown_message()
end

Production Recommendations

  • Use Redis backend for distributed systems (hammer_backend_redis)
  • Monitor rate limit violations for security threats
  • Adjust limits based on your application's usage patterns
  • Consider implementing CAPTCHA after multiple violations

Summary

Functions

Checks if login attempts are within rate limit.

Checks if magic link generation is within rate limit.

Checks if password reset requests are within rate limit.

Checks if registration attempts are within rate limit.

Gets the remaining attempts for a specific action and identifier.

Resets rate limit for a specific action and identifier.

Functions

check_login_rate_limit(email, ip_address \\ nil)

Checks if login attempts are within rate limit.

Returns :ok if the request is allowed, or {:error, :rate_limit_exceeded} if the limit is exceeded.

This function implements dual rate limiting:

  • Per-email rate limiting (prevents targeted attacks on specific accounts)
  • Per-IP rate limiting (prevents distributed brute-force attacks)

Examples

iex> PhoenixKit.Users.RateLimiter.check_login_rate_limit("user@example.com", "192.168.1.1")
:ok

# After 5 failed attempts:
iex> PhoenixKit.Users.RateLimiter.check_login_rate_limit("user@example.com", "192.168.1.1")
{:error, :rate_limit_exceeded}

check_password_reset_rate_limit(email)

Checks if password reset requests are within rate limit.

Returns :ok if the request is allowed, or {:error, :rate_limit_exceeded} if the limit is exceeded.

Password reset requests have moderate rate limits to prevent mass reset attacks while still allowing legitimate users to recover their accounts.

Examples

iex> PhoenixKit.Users.RateLimiter.check_password_reset_rate_limit("user@example.com")
:ok

# After 3 requests in 5 minutes:
iex> PhoenixKit.Users.RateLimiter.check_password_reset_rate_limit("user@example.com")
{:error, :rate_limit_exceeded}

check_registration_rate_limit(email, ip_address \\ nil)

Checks if registration attempts are within rate limit.

Returns :ok if the request is allowed, or {:error, :rate_limit_exceeded} if the limit is exceeded.

Registration has dual rate limiting:

  • Per-email rate limiting (prevents spam account creation with same email)
  • Per-IP rate limiting (prevents mass account creation from single source)

Examples

iex> PhoenixKit.Users.RateLimiter.check_registration_rate_limit("user@example.com", "192.168.1.1")
:ok

# After limit exceeded:
iex> PhoenixKit.Users.RateLimiter.check_registration_rate_limit("user@example.com", "192.168.1.1")
{:error, :rate_limit_exceeded}

get_config()

get_remaining_attempts(action, identifier)

Gets the remaining attempts for a specific action and identifier.

Returns the number of attempts remaining before rate limit is exceeded.

For login and registration actions, returns the email-based limit. For magic_link and password_reset, returns the limit for the email.

Note: With Hammer 7.x, this uses the get/2 function to retrieve the current count.

Examples

iex> PhoenixKit.Users.RateLimiter.get_remaining_attempts(:login, "user@example.com")
5

iex> PhoenixKit.Users.RateLimiter.get_remaining_attempts(:magic_link, "user@example.com")
3

reset_rate_limit(action, identifier)

This function is deprecated. Hammer 7.x removed delete_buckets. Rate limits expire after their time window..

Resets rate limit for a specific action and identifier.

DEPRECATED: Hammer 7.x removed delete_buckets with no replacement. This function now returns an error as Backend.set/3 requires positive integers (cannot set to 0).

Rate limits will naturally expire after their configured window period.

Migration

  • For testing: Use Application.put_env to disable rate limiting
  • For admin intervention: Wait for the time window to expire
  • For immediate reset: Restart the application (clears ETS tables)

See: https://hexdocs.pm/hammer/upgrade-v7.html

Examples

iex> PhoenixKit.Users.RateLimiter.reset_rate_limit(:login, "email:user@example.com")
{:error, :not_supported}