dave2wave commented on issue #535:
URL:
https://github.com/apache/tooling-trusted-releases/issues/535#issuecomment-3781155890
Below is a **clean, production-safe dual-key rate-limiting pattern** for
**Quart + ProxyFix**, applied to **your two original endpoints**.
The goal is:
> **Rate limit by *both* client IP and authenticated identity**, so:
* One user can’t exhaust limits from many IPs
* One IP can’t exhaust limits for many users
* Anonymous callers are still constrained
This fits your Apache + ProxyFix setup exactly.
---
## The dual-key strategy (how it works)
We apply **two independent limits** to the same route:
1. **IP-based limit**
* Uses `request.remote_addr`
* Protected by `ProxyFix`
* Always applies
2. **Identity-based limit**
* Uses JWT subject / client id
* Falls back to IP if unauthenticated
* Prevents user-level abuse
Whichever limit is hit **first** triggers a `429`.
---
## Key function for authenticated identity
Example assumes:
* JWT already validated upstream
* Identity available on `g.identity` (or similar)
Adjust this to match your auth wiring.
```python
from quart import request, g
def rate_limit_identity_key():
# Authenticated identity (preferred)
if hasattr(g, "identity") and g.identity:
return f"user:{g.identity}"
# Fallback for unauthenticated requests
return f"ip:{request.remote_addr}"
```
This ensures:
* Anonymous callers don’t bypass limits
* Authenticated users are isolated from each other
---
## Apply to your original endpoints
### `/jwt/create` (more permissive, but protected)
```python
@api.route("/jwt/create", methods=["POST"])
@rate_limit(10, timedelta(minutes=1)) # per-IP
@rate_limit(5, timedelta(minutes=1), key_func=rate_limit_identity_key) #
per-user
async def jwt_create(data: models.api.JwtCreateArgs) -> DictResponse:
# existing code
```
**Effective behavior**
* Max **10/min per IP**
* Max **5/min per authenticated identity**
* Anonymous callers fall back to IP limit
---
### `/tokens` (more sensitive)
```python
@api.route("/tokens", methods=["POST"])
@rate_limit(5, timedelta(minutes=1)) # per-IP
@rate_limit(2, timedelta(minutes=1), key_func=rate_limit_identity_key) #
per-user
async def token_create():
# existing code
```
**Effective behavior**
* Max **5/min per IP**
* Max **2/min per user**
* Strong protection against token abuse
---
## Why this is safe with ProxyFix
With your Apache config now forwarding:
```apache
RequestHeader add X-Forwarded-For "%{REMOTE_ADDR}s"
```
and Quart configured with:
```python
ProxyFix(..., x_for=1)
```
You get:
| Component | Trust level |
| ----------------- | ------------------------ |
| `remote_addr` | ✅ Sanitized |
| `X-Forwarded-For` | ❌ Never trusted directly |
| Rate limiter | ✅ Spoof-safe |
---
## Order of decorators (important)
Order **does not change correctness**, but this is preferred:
```python
@rate_limit(ip_limit)
@rate_limit(identity_limit)
```
Reason:
* IP limit short-circuits floods early
* Identity limit refines fairness
---
## Failure mode behavior
If either limit is exceeded:
* HTTP `429`
* Your existing 429 error handler runs
* No route code executes
---
## Recommended limits (ASF-style defaults)
| Endpoint | IP | Identity |
| ------------- | ------ | -------- |
| `/jwt/create` | 10/min | 5/min |
| `/tokens` | 5/min | 2/min |
These are conservative but automation-friendly.
---
## TL;DR
✔ Two limits, one endpoint
✔ IP + authenticated identity
✔ Safe behind ProxyFix
✔ No header spoofing
✔ Works for anonymous callers
If you want next steps, I can:
* Add **per-client-type limits** (CI vs humans)
* Show **OpenAPI documentation** for rate limits
* Add **unit tests** that assert both limits trigger correctly
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]