How to rate-limit an action

Implement RateLimitable on an action to tighten the default rate limiter on sensitive endpoints. Common cases include login, password reset, and sign-up, or adding a per-user dimension on top of the always-on per-IP bucket. For the action surface see server-actions.

The full bucket key is always "ratelimit:" + keySuffix + ":" + clientIP. KeyFunc controls the suffix. The client IP is always appended, so swapping KeyFunc adds a dimension instead of replacing IP.

Throttle a login endpoint per IP

To allow five attempts per minute per IP, return a *piko.RateLimit from RateLimit():

func (a LoginAction) RateLimit() *piko.RateLimit {
    return &piko.RateLimit{
        RequestsPerMinute: 5,
        KeyFunc:           piko.RateLimitByIP,
    }
}

Five attempts per minute fits a typical login form without locking out legitimate users who fat-finger a password.

Add a per-user dimension after authentication

To rate-limit by both user ID and IP, swap to piko.RateLimitByUser:

func (a ExportAction) RateLimit() *piko.RateLimit {
    return &piko.RateLimit{
        RequestsPerMinute: 30,
        KeyFunc:           piko.RateLimitByUser,
    }
}

Piko composes the bucket key from the registered action name (<package>.<StructName minus "Action">) plus the value KeyFunc returns plus the client IP. For an ExportAction in package accounts, that gives ratelimit:accounts.Export:<userID>:<clientIP>. The same user from two IPs gets two buckets, and two users behind one NAT'd IP get separate buckets. Anonymous callers degrade gracefully because the key function returns the remote address when no session is present.

piko.RateLimitBySession works the same way against the session ID. To define your own suffix, supply a RateLimitKeyFunc that returns the suffix string for a given request.

See also