class RateLimiter
def initialize(redis: Redis.current)
@redis = redis
end
def allow?(key, limit:, period:)
count = @redis.incr(key)
@redis.expire(key, period) if count == 1
count <= limit
end
def remaining(key, limit:)
[limit - @redis.get(key).to_i, 0].max
end
end
class SessionsController < ApplicationController
def create
limiter = RateLimiter.new
key = "rl:login:ip:#{request.remote_ip}"
unless limiter.allow?(key, limit: 20, period: 10.minutes)
render status: :too_many_requests, json: { error: 'Try again later' }
return
end
# authenticate...
end
end
A simple fixed-window rate limiter is often enough for endpoints like login, password reset, webhooks, or expensive searches. Use atomic Redis INCR + EXPIRE with a stable key and return remaining quota for UX.