User-Based Rate Limiting — Per-Account Traffic Control for Authenticated APIs
In this tutorial, you will learn about User. We cover key concepts, practical examples, and best practices to help you master this topic.
User-based Rate Limiting applies limits to authenticated users identified by their user ID, API key, or JWT claims, enabling tiered limits per subscription level, granular quotas, and per-endpoint restrictions.
What You'll Learn
- Extracting user identity from authentication tokens
- Tiered rate limits based on subscription level
- Per-endpoint user rate limiting
Why It Matters
IP-based limits punish users behind NAT and miss abuse from rotating IPs. User-based limits track the actual account, regardless of IP. Combined with tiered pricing, they enable monetization of API access.
Real-World Use
Durga Antivirus Pro offers three tiers: Free (100 req/hour), Pro (1000 req/hour), and Enterprise (10000 req/hour). The gateway extracts the user ID from the JWT, looks up their tier from the database, and applies the corresponding rate limit.
flowchart LR
Client["Authenticated\nRequest"] --> Auth["Extract User\nfrom JWT/API Key"]
Auth --> Tier["Lookup Tier\nfrom Database"]
Tier --> RL["Apply Tier\nRate Limit"]
RL -->|"Free: 100/hr"| Free["Allow"]
RL -->|"Pro: 1000/hr"| Pro["Allow"]
RL -->|"Enterprise: 10000/hr"| Enterprise["Allow"]
style Auth fill:#dbeafe,stroke:#2563eb
Extracting User Identity
from flask import Flask, request, g
import jwt
import redis
app = Flask(__name__)
redis_client = redis.Redis(host="redis", port=6379, db=0)
@app.before_request
def identify_user():
# Try JWT authentication
auth_header = request.headers.get("Authorization", "")
if auth_header.startswith("Bearer "):
token = auth_header[7:]
try:
payload = jwt.decode(token, options={"verify_signature": False})
g.user_id = payload.get("sub")
g.user_tier = payload.get("tier", "free")
except:
g.user_id = None
g.user_tier = "anonymous"
else:
g.user_id = None
g.user_tier = "anonymous"
Tiered Rate Limiting
TIER_LIMITS = {
"anonymous": {"limit": 10, "window": 3600}, # 10 req/hour
"free": {"limit": 100, "window": 3600}, # 100 req/hour
"pro": {"limit": 1000, "window": 3600}, # 1000 req/hour
"enterprise": {"limit": 10000, "window": 3600}, # 10000 req/hour
}
def check_user_rate_limit():
if not g.user_id:
return True, "unlimited"
tier_config = TIER_LIMITS.get(g.user_tier, TIER_LIMITS["free"])
limit = tier_config["limit"]
window = tier_config["window"]
key = f"user:{g.user_id}:{g.user_tier}"
now = int(__import__("time").time())
window_key = now - (now % window)
counter_key = f"rl:{key}:{window_key}"
count = redis_client.incr(counter_key)
if count == 1:
redis_client.expire(counter_key, window * 2)
g.rate_limit = limit
g.rate_limit_remaining = max(0, limit - count)
g.rate_limit_reset = window_key + window
if count > limit:
return False, "rate_limit_exceeded"
return True, "ok"
Per-Endpoint User Limits
ENDPOINT_LIMITS = {
"/api/scan": {"limit": 50, "window": 3600, "tier_overrides": {
"enterprise": {"limit": 500, "window": 3600}
}},
"/api/reports": {"limit": 10, "window": 3600},
"/api/export": {"limit": 5, "window": 3600},
}
def check_endpoint_rate_limit():
path = request.path
if path not in ENDPOINT_LIMITS:
return True
endpoint_config = ENDPOINT_LIMITS[path]
limit = endpoint_config["limit"]
window = endpoint_config["window"]
# Check tier override
tier_override = endpoint_config.get("tier_overrides", {})
if g.get("user_tier") in tier_override:
limit = tier_override[g.user_tier]["limit"]
key = f"endpoint:{g.user_id}:{path}"
count = redis_client.incr(f"rl:{key}:{int(time.time()) // window}")
return count <= limit
Rate Limit Reset Per Tier
def get_rate_limit_for_user(user_id):
tier = get_user_tier(user_id)
config = TIER_LIMITS.get(tier, TIER_LIMITS["free"])
key = f"user:{user_id}:{tier}"
now = int(time.time())
window_key = now - (now % config["window"])
count = int(redis_client.get(f"rl:{key}:{window_key}") or 0)
return {
"tier": tier,
"limit": config["limit"],
"remaining": max(0, config["limit"] - count),
"reset": window_key + config["window"],
}
Common Mistakes
1. Not Handling Unauthenticated Users
Anonymous users must still be rate limited. Apply separate (lower) limits for unauthenticated traffic.
2. Storing User Tiers in the Token
If tiers change (user upgrades), the old token has the old tier. Store tier in database and check on each request, or use short-lived tokens.
3. Not Isolating Limits Per Endpoint
A user calling /scan 100 times should not exhaust their limit for /reports. Use per-endpoint counters.
4. Forgetting About Refresh Tokens
When a user refreshes their token, ensure the rate limit counter uses the same user ID, not a new session ID.
5. Over-Limiting Internal Admin Users
Admin users and internal services should have separate, much higher limits. Never apply customer-level limits to admin traffic.
Practice Questions
- How does user-based rate limiting differ from IP-based?
- Why should user tiers be checked from a database rather than the token?
- What limits should apply to anonymous (unauthenticated) users?
- How do you handle per-endpoint rate limits for users?
- Why should admin users have separate rate limits?
Answers:
- User-based limits track the authenticated account regardless of IP. IP limits track the source address regardless of user.
- Tiers can change (upgrade/downgrade) between token issuances. Database check ensures current limits are enforced.
- Anonymous users should have very low limits (10-50 req/hour) to prevent abuse while allowing legitimate testing.
- Maintain separate counters per user per endpoint. Each endpoint path gets its own key in Redis.
- Admin operations (user management, configuration changes) should not be rate limited by customer tiers. Apply generous administrative limits.
Challenge: Design a user rate limiting system for a SaaS API with three tiers. Include per-endpoint limits (some endpoints cost 5x the rate limit units), burst allowances, and a grace period for tier upgrades.
FAQ
Mini Project
Build a user-based rate limiter with three tiers: anonymous (10 req/hour), basic (100 req/hour), premium (1000 req/hour). Use JWT authentication. Apply per-endpoint limits where /export costs 10 rate limit units per call. Return tier and remaining limits in headers.
What's Next
Continue with API Key Rate Limiting for partner and service account management, or explore Rate Limit Headers for client communication.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro