Rate Limiting¶
Enforce rate limits using the session/ratelimit package with CoreAPI policies.
Architecture¶
Request → JWT Middleware → Rate Limit Middleware → Handler
│ │
│ ├── Extract client_id from JWT
│ ├── Look up policy from PolicyStore
│ └── Check/update rate limit counters
│
└── Set claims in context (including azp/client_id)
Quick Start¶
import (
"github.com/grokify/coreforge/coreapi"
"github.com/grokify/coreforge/session/ratelimit"
)
// 1. Create policy store with policies
policyStore := coreapi.NewMemoryPolicyStore()
policyStore.CreatePolicy(ctx, coreapi.StandardPolicy)
policyStore.CreatePolicy(ctx, coreapi.PremiumPolicy)
policyStore.SetDefaultPolicy(ctx, "standard")
// 2. Bind clients to policies
policyStore.BindClientToPolicy(ctx, "mobile-app", "standard")
policyStore.BindClientToPolicy(ctx, "partner-api", "premium")
// 3. Create rate limiter with CoreAPI resolver
limiter := ratelimit.New(
ratelimit.NewMemoryStorage(),
ratelimit.WithResolver(ratelimit.NewCoreAPIResolver(policyStore)),
ratelimit.WithKeyFunc(ratelimit.CompositeKey()),
)
// 4. Apply middleware
router.Use(jwtMiddleware) // Extract JWT claims
router.Use(limiter.Middleware()) // Enforce rate limits
Storage Options¶
Memory Storage¶
For development and single-instance deployments:
Uses sliding window algorithm with automatic cleanup of expired entries.
Redis Storage¶
For distributed deployments:
Uses Lua scripts for atomic operations. Requires github.com/redis/go-redis/v9.
Key Functions¶
Key functions extract the rate limit key from the request context.
PrincipalKey¶
Rate limit per user/principal:
ClientKey¶
Rate limit per OAuth client/application:
CompositeKey¶
Rate limit per user per application (recommended):
Limit Resolvers¶
Resolvers determine the rate limit for each request.
CoreAPI Resolver¶
Look up limits from PolicyStore based on client_id:
resolver := ratelimit.NewCoreAPIResolver(policyStore,
ratelimit.WithResolverCacheDuration(5 * time.Minute),
)
limiter := ratelimit.New(storage,
ratelimit.WithResolver(resolver),
)
Static Resolver¶
Apply the same limit to all requests:
limiter := ratelimit.New(storage,
ratelimit.WithStaticLimit(ratelimit.Limit{
Requests: 100,
Window: time.Minute,
}),
)
Rate Limit Headers¶
The middleware adds standard headers to responses:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests allowed |
X-RateLimit-Remaining |
Requests remaining in window |
X-RateLimit-Reset |
Unix timestamp when limit resets |
Retry-After |
Seconds to wait (only on 429) |
Handling Rate Limit Exceeded¶
When limits are exceeded, the middleware returns:
- Status:
429 Too Many Requests - Body: JSON error with details
- Headers: Rate limit headers including
Retry-After
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again in 30 seconds.",
"retry_after": 30
}
Options¶
limiter := ratelimit.New(storage,
// Use CoreAPI resolver
ratelimit.WithResolver(resolver),
// Key extraction from JWT
ratelimit.WithKeyFunc(ratelimit.CompositeKey()),
// Custom error handler
ratelimit.WithErrorHandler(func(w http.ResponseWriter, r *http.Request, err error) {
// Custom error response
}),
// Skip rate limiting for certain requests
ratelimit.WithSkipper(func(r *http.Request) bool {
return r.URL.Path == "/health"
}),
)
Full Example¶
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/grokify/coreforge/coreapi"
"github.com/grokify/coreforge/session/jwt"
"github.com/grokify/coreforge/session/ratelimit"
)
func main() {
// Policy store setup
policyStore := coreapi.NewMemoryPolicyStore()
policyStore.CreatePolicy(ctx, coreapi.FreePolicy)
policyStore.CreatePolicy(ctx, coreapi.StandardPolicy)
policyStore.CreatePolicy(ctx, coreapi.PremiumPolicy)
policyStore.SetDefaultPolicy(ctx, "free")
// Bind OAuth clients to policies
policyStore.BindClientToPolicy(ctx, "mobile-app", "standard")
policyStore.BindClientToPolicy(ctx, "enterprise-client", "premium")
// Rate limiter
limiter := ratelimit.New(
ratelimit.NewMemoryStorage(),
ratelimit.WithResolver(ratelimit.NewCoreAPIResolver(policyStore)),
ratelimit.WithKeyFunc(ratelimit.CompositeKey()),
)
// Router
r := chi.NewRouter()
r.Use(jwt.Middleware(jwtConfig))
r.Use(limiter.Middleware())
r.Get("/api/resource", handleResource)
http.ListenAndServe(":8080", r)
}
JWT Claims¶
The rate limiter extracts claims from JWT tokens set by the JWT middleware:
| Claim | Use |
|---|---|
pid (principal ID) |
Identifies the user |
azp (authorized party) |
Identifies the OAuth client |
Ensure your JWT middleware sets these claims in the request context.