32 KiB
Extension Challenges
You've built the base project. Now make it yours by extending it with new features.
These challenges are ordered by difficulty. Start with the easier ones to build confidence, then tackle the harder ones when you want to dive deeper.
Easy Challenges
Challenge 1: Add Custom Rate Limit Headers
What to build: Add custom headers to rate limit responses beyond the standard RateLimit-* headers. Include headers like X-RateLimit-Policy (which rule was hit) and X-RateLimit-Scope (user/endpoint/global).
Why it's useful: Debugging and client-side logic. Clients can see exactly which limit they hit and adjust behavior accordingly. If they hit the per-endpoint limit vs per-user limit, they know whether to slow down across all endpoints or just the current one.
What you'll learn:
- Modifying the RateLimitResult data structure
- Adding information to HTTP responses via middleware
- Debugging rate limiting behavior
Hints:
- Look at
src/fastapi_420/types.py:66-92where RateLimitResult is defined. Add new fields for policy name and scope. - Modify the
headersproperty at lines 84-92 to include your new headers. - Test it by making requests and checking response headers:
curl -i localhost:8000/api/test
Test it works:
curl -i http://localhost:8000/api/test
# Should see:
# RateLimit-Limit: 100
# RateLimit-Remaining: 99
# X-RateLimit-Policy: default
# X-RateLimit-Scope: user
Challenge 2: Add Endpoint-Specific Limits via Decorator
What to build:
Create a decorator that applies different limits to different endpoints without using middleware configuration. Allow developers to write @rate_limit("10/minute", "100/hour") directly on route functions.
Why it's useful: More explicit than middleware configuration. Developers see limits directly in code next to the endpoint. Easier to maintain because limits live with the code they protect.
What you'll learn:
- Python decorators with parameters
- Preserving function metadata with functools.wraps
- Integrating with FastAPI's dependency injection system
Hints:
- Study the existing decorator at
src/fastapi_420/limiter.py:143-177 - The challenge is making it work without passing request explicitly. Use FastAPI's Depends() to inject request.
- Don't forget to call
functools.wraps(func)to preserve the original function's name and docstring.
Test it works:
@app.get("/custom")
@rate_limit("5/minute")
async def custom_endpoint():
return {"limited": True}
# Make 5 requests, should work
# 6th request should return HTTP 420
Challenge 3: Add Redis Key Prefix Per Environment
What to build: Automatically prefix Redis keys with environment name (dev/staging/prod) so different environments can share a Redis instance without key collisions.
Why it's useful: Cost savings. Instead of running 3 separate Redis instances, run one with namespaced keys. Development keys don't interfere with production keys.
What you'll learn:
- Redis key design patterns
- Environment-based configuration
- Key namespacing strategies
Hints:
- Modify
RateLimitKey.build()atsrc/fastapi_420/types.py:354-362to include environment prefix - Get environment from settings:
self._settings.ENVIRONMENT - Result should be:
dev:ratelimit:v1:user:...instead ofratelimit:v1:user:...
Test it works:
# Set environment
export RATELIMIT_ENVIRONMENT=staging
# Start app, make requests
# Check Redis keys
redis-cli keys "*"
# Should see: staging:ratelimit:v1:user:...
Intermediate Challenges
Challenge 4: Implement Request Cost Weighting
What to build: Allow different requests to consume different amounts of rate limit "budget." A simple GET might cost 1 point, while an expensive report generation costs 10 points. Clients with 100 points/minute can make 100 simple requests or 10 expensive ones.
Real world application: OpenAI's API uses this pattern. Different models cost different amounts of "tokens." GPT-4 is more expensive than GPT-3.5. Same concept applies to APIs where some endpoints are computationally expensive.
What you'll learn:
- Extending rate limiting beyond simple request counting
- Designing flexible APIs that account for resource consumption
- Balancing simplicity with power
Implementation approach:
-
Add cost parameter to check method
- Files to create: None (modify existing)
- Files to modify:
src/fastapi_420/limiter.py:181,src/fastapi_420/algorithms/base.py:26
-
Multiply counter by cost
- Hook into storage increment operations
- Pass cost to Lua scripts
- Update sliding window algorithm to handle weighted counts
-
Test edge cases:
- What if cost=0? (Free requests)
- What if cost=1000 but limit is 100? (Single request exhausts limit)
- What if cost is negative? (Should reject as invalid)
Hints:
- Start by adding an optional
cost: int = 1parameter tolimiter.check() - In the Lua script, change
redis.call('INCR', current_key)toredis.call('INCRBY', current_key, cost) - For testing, create endpoints with different costs:
/cheap(cost=1),/expensive(cost=10)
Extra credit:
Make cost configurable per endpoint via decorator: @limiter.limit("100/minute", cost=5)
Challenge 5: Add CAPTCHA Challenge for Suspicious Clients
What to build: When a client hits rate limits repeatedly, instead of blocking them completely, return a CAPTCHA challenge. If they solve it, allow the request through. Track CAPTCHA success rate per client.
Real world application: Cloudflare's "I'm Under Attack" mode works this way. Suspicious traffic gets CAPTCHA challenges instead of hard blocks. Reduces false positives (blocking legitimate users) while still stopping bots.
What you'll learn:
- Integrating third-party services (hCaptcha, reCAPTCHA)
- Progressive enforcement strategies
- Balancing security with user experience
Implementation approach:
-
Track violation count per client
- Add counter: "violations:{identifier}" in Redis
- Increment on rate limit exceeded
- Reset on successful CAPTCHA
-
Return CAPTCHA challenge instead of 420
- When violations > threshold (e.g., 3), return HTTP 429 with CAPTCHA challenge
- Response body includes CAPTCHA site key
- Client solves CAPTCHA, submits solution token
-
Validate CAPTCHA solution
- New endpoint: POST /verify-captcha
- Validate token with CAPTCHA API
- If valid, grant temporary bypass (store token in Redis with expiration)
-
Bypass rate limit with valid CAPTCHA
- Check for CAPTCHA bypass token in fingerprint extraction
- If present and valid, skip rate limit check
Hints:
- Use hCaptcha (simpler API than reCAPTCHA)
- Store bypass tokens with 5-minute expiration
- Track CAPTCHA solve rate:
captcha_solved / captcha_presented
Testing: Can't easily test CAPTCHA in automated tests. Instead:
- Create mock CAPTCHA verifier that always returns true
- Inject mock in test environment
- In production, use real hCaptcha API
Challenge 6: Implement Geolocation-Based Rate Limits
What to build: Apply different rate limits based on client geographic location. Stricter limits for regions with high bot activity (e.g., limit clients from certain countries to 10/minute vs 100/minute for domestic traffic).
Real world application: E-commerce sites often see bot traffic primarily from certain regions. Applying regional limits reduces fraud without impacting legitimate international customers.
What you'll learn:
- IP geolocation databases (MaxMind GeoIP2)
- Policy-based rate limiting
- Geographic discrimination considerations (be careful with this)
Implementation approach:
-
Add geolocation lookup
- Library:
geoip2with MaxMind database - On fingerprint extraction, lookup IP address
- Add country code to FingerprintData
- Library:
-
Define geo-based policies
- Config:
GEO_LIMITS = {"US": "100/minute", "CN": "10/minute", "default": "50/minute"} - Load from environment or config file
- Config:
-
Apply policy at rate limit check
- In LayeredDefense, check client's country code
- Select appropriate limit from GEO_LIMITS
- Fall back to default if country not configured
-
Monitor and adjust
- Log rate limit violations by country
- Dashboard showing requests/violations per region
Hints:
- MaxMind GeoLite2 database is free but requires registration
- Cache geo lookups (IP to country mapping doesn't change frequently)
- Consider privacy implications. Logging IP addresses with country codes might be PII in some jurisdictions.
Ethical considerations: Geo-based limiting can be discriminatory. Only use it for legitimate security purposes. Document why certain regions have stricter limits. Consider offering CAPTCHA challenge instead of hard blocking.
Advanced Challenges
Challenge 7: Build a Rate Limit Dashboard
What to build: Web dashboard showing real-time rate limiting stats. Metrics include requests/sec, top violators, circuit breaker status, algorithm performance. Built with FastAPI + htmx or React.
Why this is hard: Requires collecting metrics without impacting rate limiting performance. Need efficient aggregation of high-frequency events. Must handle dashboard queries without slowing down rate limit checks.
What you'll learn:
- High-performance metrics collection
- Time-series data aggregation
- Building admin dashboards
- WebSocket or Server-Sent Events for real-time updates
Architecture changes needed:
┌─────────────────┐
│ Rate Limiter │
│ (existing) │
└────────┬────────┘
│
├─ Check limit (fast path)
│
└─ Emit metrics (async, non-blocking)
↓
┌────────────────┐
│ Metrics Buffer │
│ (in-memory) │
└────────┬───────┘
│
Batch write every 5 seconds
↓
┌────────────────┐
│ Redis TimeSeries│
│ or InfluxDB │
└────────┬───────┘
│
↓
┌────────────────┐
│ Dashboard │
│ (FastAPI) │
└────────────────┘
Implementation steps:
Phase 1: Metrics Collection (3-5 hours)
- Add metrics emitter to rate limiter
- Batch metrics in memory buffer
- Flush to Redis every 5 seconds
- Metrics: requests_total, violations_total, latency_histogram
Phase 2: Aggregation (3-5 hours)
- Create background task to aggregate 5-second metrics into 1-minute/1-hour/1-day buckets
- Store in Redis sorted sets:
ZADD metrics:requests:minute:{timestamp} {count} {endpoint} - Retention: Keep 5-second data for 10 minutes, 1-minute data for 24 hours, 1-hour data for 30 days
Phase 3: Dashboard Backend (4-6 hours)
- FastAPI endpoints:
GET /api/metrics/summary- Current state (last minute stats)GET /api/metrics/timeseries?metric=requests&window=1h- Historical dataGET /api/metrics/top-violators?limit=10- Clients with most violations
- WebSocket endpoint:
/ws/metrics- Real-time updates
Phase 4: Dashboard Frontend (6-8 hours)
- Chart.js or Recharts for time-series graphs
- Auto-refresh every 5 seconds
- Cards showing: Total requests, Rate limit violations, Circuit breaker status, Top endpoints
- Table of recent violations with client fingerprint (truncated), endpoint, timestamp
Gotchas:
- Don't block rate limit checks: Metrics collection must be async. Use
asyncio.create_task()to fire-and-forget. - Buffer overflow: If metrics buffer grows too large (million events), drop oldest or sample.
- Redis memory: Time-series data grows fast. Set expiration policies.
Success criteria: Your implementation should:
- Collect metrics without adding >5ms latency to rate limit checks
- Display requests/sec updated every 5 seconds
- Show top 10 rate limit violators with endpoint breakdown
- Visualize circuit breaker state transitions
- Handle 10k requests/sec without overloading dashboard backend
Challenge 8: Implement Distributed Circuit Breaker
What to build: Circuit breaker that shares state across multiple API servers using Redis. When one server trips the circuit, all servers immediately enter the same state. Current implementation is per-process only.
Estimated time: 2-3 days for full implementation with testing
Prerequisites: You should have completed Challenge 4 (request cost weighting) and Challenge 7 (dashboard) first because understanding metrics is crucial for distributed coordination.
What you'll learn:
- Distributed systems coordination
- Redis pub/sub for event propagation
- Eventually consistent state management
- Handling network partitions and race conditions
Planning this feature:
Before you code, think through:
- Consensus: How do servers agree on circuit state? (Redis as single source of truth)
- Propagation delay: Server A trips circuit, how long until Server B knows? (Pub/sub gives ~100ms)
- Failure modes: What if Redis connection fails? (Fall back to local circuit breaker)
- Race conditions: Multiple servers trying to trip circuit simultaneously (Use Redis SET NX)
High level architecture:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Server 1 │ │ Server 2 │ │ Server 3 │
│ Circuit │ │ Circuit │ │ Circuit │
│ Breaker │ │ Breaker │ │ Breaker │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└──────────────────────┼──────────────────────┘
│
┌──────────▼───────────┐
│ Redis │
│ Circuit State: │
│ - is_open │
│ - trip_time │
│ - request_count │
│ │
│ Pub/Sub Channel: │
│ circuit:events │
└──────────────────────┘
Implementation phases:
Phase 1: Shared State (6-8 hours)
- Store circuit state in Redis hash:
circuit:state - Fields: is_open (0/1), last_trip_time, failure_count, request_count
- Each server reads state before checking thresholds
- Use Redis TTL to auto-reset after recovery_time
Phase 2: Atomic Tripping (4-6 hours)
- Use Lua script for atomic trip operation:
local current_count = redis.call('GET', 'circuit:requests')
if current_count >= threshold then
redis.call('HSET', 'circuit:state', 'is_open', '1')
redis.call('HSET', 'circuit:state', 'trip_time', now)
redis.call('PUBLISH', 'circuit:events', 'TRIPPED')
return 1
end
return 0
- Only one server successfully trips, others see is_open=1 immediately
Phase 3: Event Propagation (5-7 hours)
- Subscribe to Redis pub/sub channel:
circuit:events - Events: TRIPPED, HALF_OPEN, CLOSED
- Background task listens for events, updates local cache
- Local cache reduces Redis reads (check local first, Redis if stale)
Phase 4: Graceful Degradation (4-6 hours)
- If Redis connection fails, fall back to local circuit breaker
- Log warning: "Distributed circuit breaker unavailable, using local"
- When Redis reconnects, sync local state with distributed state
- Handle case where local circuit is open but distributed is closed (trust distributed)
Known challenges:
-
Clock Skew
- Problem: Servers have slightly different clocks. Server A thinks circuit should close at 12:00:30, Server B thinks 12:00:31.
- Hint: Use Redis TIME command to get server time, not local time. Or use TTL-based expiration instead of timestamp comparison.
-
Network Partition
- Problem: Server C can't reach Redis, falls back to local circuit. Local circuit trips at lower threshold, blocks legitimate traffic.
- Hint: Add "confidence" metric. If Redis connection is flaky, increase thresholds or disable circuit entirely (fail-open).
-
Thundering Herd
- Problem: Circuit opens, 100 servers all try to enter half-open state simultaneously after recovery_time.
- Hint: Randomize recovery time:
recovery_time ± random(0, 5)so servers stagger their retry attempts.
Success criteria: Your implementation should:
- Trip circuit across all servers within 500ms of threshold exceeded
- Sync state even if a server restarts (reads from Redis on startup)
- Gracefully handle Redis connection failure (fall back to local)
- Support manual circuit state changes via Redis CLI:
HSET circuit:state is_open 1 - Pass load test: 10 servers, 100k requests/sec, circuit trips correctly across all
Challenge 9: Build Custom Algorithm - Generic Cell Rate Algorithm (GCRA)
What to build: Implement the GCRA algorithm used by ATM networks and some high-performance rate limiters. More precise than token bucket, allows bursting within a defined "cell delay variation tolerance."
Estimated time: 3-4 days including research, implementation, and thorough testing
Prerequisites: Strong understanding of token bucket and sliding window algorithms. Read the GCRA spec before starting.
What you'll learn:
- Telecoms network algorithms
- Sub-second precision rate limiting
- Continuous vs discrete time modeling
- Algorithm correctness proofs
How GCRA works:
GCRA tracks "Theoretical Arrival Time" (TAT). Each request should arrive no earlier than TAT. If request arrives at time T:
if T >= TAT:
TAT = max(TAT, T) + 1/rate
ALLOW
else:
DENY
With burst tolerance, allow requests that arrive up to limit time units early:
if T >= TAT - limit:
TAT = max(TAT, T) + 1/rate
ALLOW
else:
DENY
Implementation:
- Create algorithm file:
src/fastapi_420/algorithms/gcra.py - Define state: Store TAT in Redis/memory
- Implement check method:
async def check(self, storage, key, rule, timestamp=None):
now = timestamp or time.time()
rate = rule.requests / rule.window_seconds # requests per second
limit = rule.requests # burst tolerance
tat = await storage.get_gcra_state(key)
if tat is None:
tat = now
if now >= tat - limit / rate:
# Allow
new_tat = max(tat, now) + 1/rate
await storage.set_gcra_state(key, new_tat, ttl=rule.window_seconds)
remaining = calculate_remaining(new_tat, now, rate, limit)
return RateLimitResult(allowed=True, remaining=remaining, ...)
else:
# Deny
return RateLimitResult(allowed=False, retry_after=tat - now, ...)
- Add storage methods:
get_gcra_state()andset_gcra_state()to storage backends - Write comprehensive tests: Edge cases like clock adjustments, very high rates, fractional rates
Testing strategy:
# Test burst tolerance
rule = RateLimitRule(requests=10, window_seconds=10) # 1 req/sec, burst of 10
algo = GCRAAlgorithm()
# Should allow 10 immediate requests
for i in range(10):
assert algo.check(storage, "test", rule, timestamp=0).allowed
# 11th should deny
assert not algo.check(storage, "test", rule, timestamp=0).allowed
# After 1 second, should allow 1 more
assert algo.check(storage, "test", rule, timestamp=1.0).allowed
Compare with token bucket: Both algorithms allow bursting, but GCRA is more precise. Token bucket updates in discrete increments (add X tokens every Y seconds). GCRA uses continuous time (TAT can be any float value).
Mix and Match
Combine features for bigger projects:
Project Idea 1: Full Rate Limiting Dashboard with Geo Limits
- Combine Challenge 7 (Dashboard) + Challenge 6 (Geolocation)
- Add map visualization showing requests by country
- Admin panel to adjust geo limits in real-time
- Result: Production-ready rate limiting with geographic policies
Project Idea 2: Cost-Weighted Limits with CAPTCHA Bypass
- Combine Challenge 4 (Cost Weighting) + Challenge 5 (CAPTCHA)
- Expensive operations cost more points
- When points exhausted, offer CAPTCHA to get temporary boost
- Result: Flexible system that allows bursts with verification
Real World Integration Challenges
Integrate with Prometheus for Monitoring
The goal: Export rate limiting metrics to Prometheus for visualization in Grafana. Track requests, violations, algorithm latency, storage health.
What you'll need:
- prometheus-client library
- Grafana dashboard JSON
- Understanding of metric types (counter, gauge, histogram)
Implementation plan:
- Add prometheus_client to dependencies
- Create metrics in
src/fastapi_420/metrics.py:
from prometheus_client import Counter, Histogram
requests_total = Counter(
'ratelimit_requests_total',
'Total requests checked',
['algorithm', 'allowed']
)
check_latency = Histogram(
'ratelimit_check_duration_seconds',
'Time to check rate limit'
)
- Instrument code:
requests_total.labels(algorithm='sliding_window', allowed='true').inc() - Expose metrics endpoint:
@app.get("/metrics")returnsprometheus_client.generate_latest() - Configure Prometheus scrape config, point at
/metrics
Watch out for:
- High cardinality labels (don't include user IDs in labels, use <10 unique values per label)
- Metric name conventions (use underscores, suffix with unit)
- Performance impact (prometheus_client is fast but not free, adds ~50μs per metric update)
Deploy to Kubernetes
The goal: Run the rate-limited API in Kubernetes with Redis, monitoring, and auto-scaling.
What you'll learn:
- Kubernetes deployments and services
- ConfigMaps and Secrets for configuration
- Horizontal Pod Autoscaler
- Redis deployment with persistence
Steps:
- Create Dockerfile for API:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY src/ ./src/
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0"]
- Kubernetes manifests:
# redis-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
template:
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
# api-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
template:
spec:
containers:
- name: api
image: your-api:latest
env:
- name: RATELIMIT_REDIS_URL
value: "redis://redis:6379"
ports:
- containerPort: 8000
# api-service.yaml
apiVersion: v1
kind: Service
metadata:
name: api
spec:
selector:
app: api
ports:
- port: 80
targetPort: 8000
type: LoadBalancer
- ConfigMap for settings:
apiVersion: v1
kind: ConfigMap
metadata:
name: ratelimit-config
data:
RATELIMIT_ALGORITHM: "sliding_window"
RATELIMIT_DEFAULT_LIMIT: "100/minute"
RATELIMIT_CIRCUIT_THRESHOLD: "10000"
- Horizontal Pod Autoscaler:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Production checklist:
- Redis has persistent volume for data
- Secrets used for sensitive config (not ConfigMap)
- Resource limits set (CPU/memory)
- Liveness and readiness probes configured
- Logging goes to stdout (captured by Kubernetes)
- Metrics endpoint exposed for Prometheus scraping
Performance Challenges
Challenge: Handle 100k Requests/Second
The goal: Optimize the rate limiter to handle 100k requests/sec on a single machine without falling over.
Current bottleneck: At 10k req/sec, Redis network latency becomes limiting factor. Each request = 1 Redis call = 1ms round trip = max 1000 req/sec per connection.
Optimization approaches:
Approach 1: Connection Pooling
- How: Increase
REDIS_MAX_CONNECTIONSfrom 100 to 1000 - Gain: 100x parallelism, can handle 100k req/sec
- Tradeoff: Redis connection overhead, more memory
Approach 2: Local Caching with Eventual Consistency
- How: Cache rate limit state in-memory for 100ms. Only sync with Redis every 100ms.
- Gain: Reduce Redis calls by 90%, max 10x improvement
- Tradeoff: Accuracy drops to ~95%. Two servers might allow 110/100 requests in a window.
Approach 3: Batching
- How: Accumulate requests for 10ms, send to Redis in single pipeline
- Gain: Reduce network overhead, ~5x improvement
- Tradeoff: 10ms added latency, complexity
Benchmark it:
# Load test with wrk
wrk -t12 -c400 -d30s http://localhost:8000/api/test
# Should see:
# Requests/sec: 100000
# Latency avg: 4ms
# Latency 99th: 15ms
Target metrics:
- Throughput: >100k requests/sec
- Latency p50: <5ms
- Latency p99: <20ms
Challenge: Reduce Memory Usage by 90%
The goal: Reduce memory consumption for 1 million active rate limit keys from ~100MB to ~10MB.
Current usage: Each key stores:
- Python dict entry: 56 bytes
- WindowEntry: 24 bytes
- String key (~50 chars): 50 bytes Total: ~130 bytes/key * 1M keys = 130MB
Optimization areas:
Area 1: Use Redis instead of Memory
- What's inefficient: Python objects have overhead
- How to fix: Store in Redis (more memory efficient)
- Savings: 70% reduction
Area 2: Compress Keys
- What's inefficient: Keys like "ratelimit:v1:user:POST:/api/endpoint:192.168.1.1:Mozilla...:60" are 100+ chars
- How to fix: Hash keys to 16-char hex strings
- Savings: 80% reduction in key size
Area 3: Use Slots
- What's inefficient: Python's
__dict__attribute on dataclasses - How to fix: Add
__slots__to WindowEntry, RateLimitResult - Savings: 30% reduction in object size
Security Challenges
Challenge: Add JWT-Based Rate Limit Tiers
What to implement: Different rate limits based on JWT claims. Free tier: 10/minute. Paid tier: 100/minute. Enterprise: 1000/minute.
Threat model: This protects against:
- Free tier abuse (creating many accounts to get more quota)
- Fair resource allocation (paying customers get what they paid for)
Implementation:
- Extract tier from JWT:
# In auth extractor
payload = jwt.decode(token, secret, algorithms=['HS256'])
tier = payload.get('tier', 'free') # free, paid, enterprise
- Add tier to fingerprint:
# In FingerprintData
tier: str | None = None
- Select limit based on tier:
# In RateLimiter.check
tier_limits = {
'free': ['10/minute'],
'paid': ['100/minute'],
'enterprise': ['1000/minute'],
}
rules = [RateLimitRule.parse(l) for l in tier_limits.get(tier, tier_limits['free'])]
Testing the security:
- Try to forge JWT with tier=enterprise
- Should fail signature validation
- Try to create free account, abuse endpoint
- Should hit 10/minute limit
- Verify paid users can exceed free tier limits
Challenge: Pass OWASP API Security Top 10
The goal: Audit the rate limiter against OWASP API Security Top 10 and fix any gaps.
Current gaps:
- API4:2023 - Unrestricted Resource Consumption: Partially covered by rate limiting. Gap: No per-IP limits on unauthenticated endpoints.
- API5:2023 - Broken Function Level Authorization: Gap: Circuit breaker bypass doesn't check if "authenticated" user actually has permission.
- API8:2023 - Security Misconfiguration: Gap: No validation that production has Redis configured.
Remediation:
- Add per-IP limits for unauthenticated:
if not fingerprint.auth_identifier:
# Stricter limits for anonymous
rules = [RateLimitRule.parse('10/minute')]
- Add permission check to circuit bypass:
def _should_bypass_circuit(self, context):
if not context.is_authenticated:
return False
# Check actual permissions, not just authentication
return context.has_permission('circuit.bypass')
- Add production validation:
@model_validator(mode='after')
def validate_production(self):
if self.ENVIRONMENT == 'production':
if not self.storage.REDIS_URL:
raise ValueError("Production requires Redis")
return self
Contribution Ideas
Finished a challenge? Share it back:
- Fork the repo
- Implement your extension in a feature branch:
git checkout -b feature/captcha-challenge - Document it - Add to learn/ folder explaining how it works
- Submit a PR with:
- Your implementation
- Tests proving it works
- Documentation in learn/ folder
- Example usage in examples/
Good extensions might get merged into the main project. Even if not merged, you'll have a portfolio piece showing real-world security engineering.
Challenge Yourself Further
Build Something New
Use the concepts you learned here to build:
- API Gateway with rate limiting - Reverse proxy that adds rate limiting to any backend API
- Database query rate limiter - Limit queries/second to prevent database overload
- Distributed task queue throttler - Rate limit background job execution
Study Real Implementations
Compare your implementation to production tools:
- Kong API Gateway - Supports multiple rate limiting strategies, read their Lua plugin code
- Cloudflare Workers - Edge-based rate limiting, study their architecture blog posts
- GitHub API - Read their rate limiting docs, understand their tier system
Read their code, understand their tradeoffs, steal their good ideas.
Write About It
Document your extensions:
- Blog post: "Adding Geolocation-Based Rate Limiting to FastAPI"
- Tutorial: "How I Built a Rate Limiting Dashboard in a Weekend"
- Comparison: "Sliding Window vs GCRA: Performance Benchmarks"
Teaching others is the best way to verify you understand it.
Getting Help
Stuck on a challenge?
-
Debug systematically
- What did you expect? (Specific behavior)
- What actually happened? (Logs, errors, wrong output)
- What's the smallest test case that reproduces it?
-
Read the existing code
- Similar features already implemented? (Look at algorithms/ for patterns)
- Tests showing how components work? (tests/test_*.py has examples)
-
Search for similar problems
- GitHub issues on similar projects
- Stack Overflow for specific errors
- Redis/FastAPI docs for API questions
-
Ask for help
- Open a GitHub discussion with:
- Challenge you're working on
- What you've tried
- Specific error or unexpected behavior
- Your hypothesis about what's wrong
- Don't just paste errors. Explain your understanding.
- Open a GitHub discussion with:
Challenge Completion
Track your progress:
- Easy Challenge 1: Custom headers
- Easy Challenge 2: Decorator syntax
- Easy Challenge 3: Environment prefixes
- Intermediate Challenge 4: Request cost weighting
- Intermediate Challenge 5: CAPTCHA integration
- Intermediate Challenge 6: Geolocation limits
- Advanced Challenge 7: Rate limit dashboard
- Advanced Challenge 8: Distributed circuit breaker
- Advanced Challenge 9: GCRA algorithm
Completed all of them? You've mastered rate limiting. Time to build something new or contribute back to the community.