The 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 (when SetHeaders 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:
With trusted proxies configured and the immediate peer trusted, the 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-process TokenBucketLimiter 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:
See go-security-redis and docs/INTEGRATION.md for the full adapter spec.