ratelimit package provides an in-process token-bucket limiter
(Bucket4j-adjacent), trusted-proxy-aware client-IP extraction, and a login
throttle decoupled from the rest of the auth stack. A separate Store
contract lets a distributed (Redis) fixed-window counter plug in for
multi-instance deployments.
Import
Token bucket
TokenBucketLimiter is self-contained — there’s no pluggable Store for it.
Memory is bounded internally (LRU + TTL eviction); it needs no backend to run
on a single instance.
HTTP middleware
securityhttp.WithRateLimit takes the ratelimit.Config directly (not a
bare limiter) — the config is what carries Trusted, FailOpen, and header
behavior through to the assembled chain.
Security warning: do not enable
SetHeaders on a login/authentication
throttle path. Retry-After (and the remaining-count headers) tell an
attacker exactly when to resume, turning the throttle into a precise clock for
credential stuffing and password spraying. Leave it false on auth endpoints;
it’s only appropriate for cooperative API quota signalling.
Response headers
Only these are emitted (whenSetHeaders is true) — there is no
X-RateLimit-Reset:
Trusted proxies (the spoofing gate)
TrustedProxies is empty by default, which means X-Forwarded-For is
ignored entirely and the client IP is always r.RemoteAddr — construct it
only for the CIDRs of your actual load balancer / reverse proxy:
X-Forwarded-For chain is walked right-to-left, skipping trusted hops, and
the first untrusted address is the client. Otherwise it falls back to
RemoteAddr. It never trusts the leftmost (attacker-controlled) entry from an
untrusted peer.
Login throttle
LoginThrottle slows credential stuffing per username+IP composite key
without locking accounts — locking is lockout’s job. It deliberately does
not import web/auth: you wire it in with Gate (from a custom handler) or
Wrap (as middleware fronting the login path).
Wrap("username", 4096) peeks the username from a bounded (4096-byte) copy of
the request body without consuming it — the body is restored for the
downstream login handler, and an oversized body yields 413 rather than
buffering unbounded input. For a custom handler, call Gate directly:
Distributed store (Redis)
The in-processTokenBucketLimiter has no distributed mode. For multi-instance
deployments, ratelimit.Store is a separate fixed-window contract
(IncrAndExpire / Reset) that a Redis adapter implements — it does not plug
into TokenBucketLimiter; a fixed-window limiter is a different algorithm
chosen because it’s the primitive Redis can implement atomically (INCR +
EXPIRE NX in one round trip). The bundled ratelimit.MemoryStore is the
in-process reference implementation of this contract.
IncrAndExpire must be atomic — a read-then-write increment is a
rate-limit-bypass vulnerability (an attacker parallelizes requests to exceed
the limit). The exported ratelimit.StoreConformance harness proves an
adapter satisfies this:
docs/INTEGRATION.md for the full
adapter spec.