App Logo
Plugins

Rate Limit Plugin

Provide request rate limiting with multiple storage backends and per-endpoint custom rules.

Overview

The Rate Limit plugin provides request rate limiting with multiple storage backends and a flexible custom rule system to protect your application from abuse and brute force attacks. It uses a fixed-window counter algorithm and supports method-level rules, parameterized path matching, and wildcard route patterns.

Features

  • Multiple Storage Backends — In-memory (default), Redis, or Database storage
  • Custom Rules Per Endpoint — Different limits for different paths or methods
  • Method-Level Rules — Apply rules to specific HTTP methods (e.g., POST:/auth/login)
  • Parameterized Path Matching — Rules match dynamic route parameters like {id} and wildcards via normalization
  • Auto-Pattern Fallback — Parameterized or wildcard routes automatically share a single rate limit counter
  • Per-Key Stored Rules — Other plugins can set rate limits for specific API keys or identifiers
  • Client IP Hashing — Optional SHA-256 hashing of client IPs for privacy compliance
  • Client IP-Based Limiting — Tracks requests by client IP address
  • Standard HTTP Rate Limit Headers — Client-aware headers for rate limit information
  • Automatic Cleanup — Removes expired entries to prevent memory leaks
  • Fail-Open Behavior — Allows requests when storage provider errors occur
  • Production Auto-Enable — Automatically enabled when GO_ENVIRONMENT=production

Configuration

Standalone Mode:

[plugins.ratelimit]
enabled = true
window = "1m"              # Time window for rate limiting (default: 1 minute)
max = 100                  # Max requests per window (default: 100)
prefix = "ratelimit:"      # Storage key prefix (default: "ratelimit:")
provider = "memory"        # Options: memory, redis, database (default: memory)
hash_client_ip = false     # Hash client IPs with SHA-256 (default: false)

# In-Memory storage configuration (default provider)
[plugins.ratelimit.memory]
cleanup_interval = "1m"    # How often to clean expired entries (default: 1 minute)

# Database storage configuration (optional)
[plugins.ratelimit.database]
cleanup_interval = "1m"    # How often to clean expired entries (default: 1 minute)

# Custom rules for specific endpoints
[plugins.ratelimit.custom_rules]
# Method-specific rule: strict limit on POST /auth/sign-in
"POST:/api/auth/sign-in" = { window = "15m", max = 10 }
# Path-only rule: applies to all HTTP methods on /api/public
"/api/health" = { disabled = true }
# Parameterized path: matches any organization ID under /organizations/{organization_id}
"/api/auth/organizations/{organization_id}" = { window = "5m", max = 30 }

Library Mode:

import (
  ratelimitplugin "github.com/Authula/authula/plugins/rate-limit"
)

ratelimitplugin.New(ratelimitplugin.RateLimitPluginConfig{
  Enabled:      true,
  Provider:     ratelimitplugin.RateLimitProviderRedis,
  Window:       time.Minute,
  Max:          100,
  Prefix:       "ratelimit:",
  HashClientIP: true,
  CustomRules: map[string]ratelimitplugin.RateLimitRule{
    "POST:/api/auth/sign-in": {
      Window: 15 * time.Minute,
      Max:    10,
    },
    "/api/health": {
      Disabled: true,
    },
  },
})

Storage Providers

ProviderPerformancePersistenceBest For
MemoryFastestNoSingle-instance deployments
DatabaseModerateYesStrict audit requirements
RedisFastYesMulti-instance deployments

API Reference

The Rate Limit plugin does not expose its own HTTP endpoints. Instead, it applies rate limiting as a hook to all incoming requests.

Custom Rules

Custom rules override the default rate limit for specific endpoints using a multi-strategy matching algorithm.

Each rule key is either:

  • A method+path pattern (e.g., POST:/api/auth/sign-in) — only applies to that HTTP method
  • A path-only pattern (e.g., /api/health) — applies to all HTTP methods on that path

Rule Format

[plugins.ratelimit.custom_rules]
# Method-specific: only applies to POST requests
"POST:/api/auth/sign-in" = { window = "15m", max = 10 }

# Path-only: applies to all HTTP methods on this path
"/api/health" = { disabled = true }

Matching Algorithm

When a request arrives, the plugin builds candidate patterns from the request and evaluates them in order. The first match wins and its rule is applied.

Patterns evaluated (in order):

  1. The router's registered path pattern (e.g., GET:/api/auth/organizations/{organization_id})
  2. The actual request method and path (e.g., GET:/api/auth/organizations/my-org)

For each pattern, the plugin tries four matching strategies:

PriorityStrategyExample
1Exact map lookup on the full patterncustomRules["GET:/api/auth/organizations/{organization_id}"]
2Normalized match with segment count guard{organization_id}{*}, then compare normalized keys and segment count
3Path-only exact match (strips method prefix)customRules["/api/auth/organizations/{organization_id}"]
4Normalized path-only match with segment guardStrip method, normalize params, verify segments

Parameter Normalization

Path parameters like {id}, {organization_id}, and wildcards * are normalized to {*} during matching. This allows rules to match routes regardless of the parameter name:

Route PatternNormalized Form
GET:/api/auth/organizations/{organization_id}/members/{member_id}GET:/api/auth/organizations/{*}/members/{*}
GET:/api/*GET:/api/{*}
/users/{id}/users/{*}

Segment Count Guard

Before a normalized match is accepted, the plugin verifies the request path has the same number of segments as the rule key. This prevents a rule like /api/auth/organizations/{organization_id} from incorrectly matching a 3-segment path like /api/auth/organizations.

Rule Application

Once a rule is matched:

  • If disabled = true, rate limiting is completely skipped for that endpoint
  • If window is set (> 0), it overrides the default window
  • If max is set (> 0), it overrides the default max limit
  • The rate limit counter is keyed by {prefix}:{clientIP}:{ruleHash} so each rule has its own counter per client

Auto-Pattern Fallback

If no custom rule matches but the request's route pattern contains parameters ({ or *), the plugin automatically keys the rate limit counter by the route pattern rather than the full path. This ensures all requests to a parameterized route (e.g., /api/auth/organizations/{organization_id}) share a single counter instead of each dynamic path having its own. Without this, an attacker could bypass limits by varying the path parameter.

Per-Key Stored Rules

Other plugins (e.g., API Key plugin) can set dynamic rate limits for specific identifiers at runtime by storing a RateLimitRuleContext in the request context. These rules are persisted by the storage provider and evaluated in the HookBefore phase, allowing per-client rate limits that survive restarts.

HTTP Response Headers

The plugin adds standard rate limit headers to all responses:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the window
X-RateLimit-RemainingRemaining requests in current window
X-RateLimit-ResetUnix timestamp when the window resets
X-Retry-AfterSeconds to wait before retry (only when rate limited)

When rate limited:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1738302000
X-Retry-After: 45

{
  "message": "rate limit exceeded",
  "retry_after": 45,
  "limit": 100,
  "remaining": 0
}

Database Schema

Table: rate_limits

FieldTypeKeyDescription
keystringPKRate limit key (e.g., client IP)
countint-Current request count
expires_attimestamp-When the entry expires
  • SQLite: this table is set as a temp table to ensure it is automatically cleared on server restart.
  • PostgreSQL: this table is set as an unlogged table to improve performance, with a periodic cleanup process to remove expired entries.
  • MYSQL: this table is set as a MEMORY table to store data in memory for fast access, with automatic cleanup of expired entries.

Table: rate_limit_rules

FieldTypeKeyDescription
keystringPKRule key (e.g., API key identifier)
window_secondsint-Rate limit window in seconds
max_requestsint-Maximum requests allowed per window

This table stores per-key rate limit rules set by other plugins at runtime (e.g., API Key plugin). When the same storage constraints apply (temp/unlogged/MEMORY) as rate_limits above.

Migrations are automatically handled when the plugin is initialized.


Plugin Capabilities

This plugin doesn't have any capabilities. However, it registers three hooks to provide rate limiting:

HookOrderPurpose
HookOnRequest0Applies IP-based endpoint rate limiting using global and custom rules
HookBefore15Evaluates per-key stored rules set by other plugins (e.g., API Key)
HookAfter0Persists new per-key rules set via request context during the request lifecycle

Security Recommendations

  • Client IP Detection — Uses client IP from the request. Configure trusted_proxies and trusted_headers in the global security config if behind a load balancer.
  • Fail-Open — Provider errors allow requests through to prevent denial-of-service from rate limiter failures.
  • OPTIONS Requests — Automatically skipped to prevent blocking CORS preflight requests.
  • Stricter Limits for Auth Endpoints — Apply lower limits to sensitive endpoints on other plugins to prevent brute force attacks.
  • Use Redis for Multi-Instance — When running multiple server instances, use Redis to share rate limit state across all instances.

On this page