Skip to content
Kona

Rate limiting

Rate limiting caps how often a client may hit a route — login endpoints, public APIs, expensive reports. Kona’s RateLimiter tracks hits in memory by key; ThrottleRequests middleware turns a named limiter into HTTP responses with standard headers.

Artifact: org.konaframework:kona-auth. AuthServiceProvider registers a shared RateLimiter.

import org.konaframework.http.HttpKernel
import org.konaframework.http.Router
import org.konaframework.security.RateLimit
import org.konaframework.security.RateLimiter
import org.konaframework.security.ThrottleRequests
val limiter = app.resolve(RateLimiter::class.java)
limiter.forName("login") { request ->
RateLimit.perMinute(5) // or RateLimit(5, 60)
}
val router = app.resolve(Router::class.java)
router.routes {
group(prefix = "/auth", middleware = listOf(ThrottleRequests(limiter, "login"))) {
post("/login") { /* … */ }
}
}

When the limit is exceeded, the middleware throws RateLimitExceededException — HTTP 429 with a Retry-After header (seconds until the window resets).

Successful responses gain:

  • X-RateLimit-Limit — max attempts in the window
  • X-RateLimit-Remaining — attempts left after this hit

Register limiters with for / forName (Java-friendly alias). Each registration is a callback (Request) -> RateLimit so limits can depend on path, user, or plan:

limiter.forName("api") { request ->
val userId = request.getAttribute("user")?.toString()
if (userId != null) RateLimit(120, 60) else RateLimit(30, 60)
}

ThrottleRequests builds the hit key as {limiterName}:{clientKey}. The default client key is:

  1. X-Forwarded-For
  2. else X-Real-IP
  3. else request attribute "ip"
  4. else "global"

Override with a custom resolver:

ThrottleRequests(limiter, "login") { req ->
req.input("email") ?: req.header("X-Forwarded-For") ?: "unknown"
}
val key = "reports:user-42"
if (!limiter.attempt(key, maxAttempts = 10, decaySeconds = 60)) {
val wait = limiter.availableIn(key)
throw RateLimitExceededException(wait)
}
// work…
limiter.clear(key) // reset after success, if desired

Other helpers: hit, tooManyAttempts, retriesLeft, clearAll.

Situation Result
Unknown limiter name in middleware IllegalArgumentException
Too many attempts RateLimitExceededException (429 + Retry-After)
Process restart In-memory counters reset (fine for single-node; shared stores land with later infrastructure milestones)

Place throttle middleware outside expensive handlers but inside identity middleware when the key depends on the authenticated user.