Api Rate Abuse in Django with Oauth2
Api Rate Abuse in Django with Oauth2 — how this specific combination creates or exposes the vulnerability
Rate abuse in Django when OAuth2 is in use often centers on how tokens are issued, validated, and associated with resource owners and scopes. Without explicit rate controls at the token endpoint and per-resource endpoints, an attacker can exploit the OAuth2 flow to amplify requests and exhaust server capacity or degrade performance.
Consider the OAuth2 authorization code flow: a client redirects the user to an authorization endpoint, receives an authorization code, and exchanges it for an access token. If the token endpoint (e.g., /oauth/token) does not enforce per-client or per-user rate limits, an attacker can perform token brute-force or authorization code injection attempts at scale. Similarly, once access tokens are issued, endpoints protected by scopes or token introspection may still lack request-rate limits, enabling token replay or credential stuffing against specific resources.
In Django, common patterns involve using packages such as django-oauth-toolkit or django-allauth combined with custom views. If these integrations do not explicitly throttle token issuance or resource access, the framework’s typical middleware ordering does not inherently protect against bursts of authenticated or semi-authenticated requests. For example, even with OAuth2 protecting an endpoint, an attacker who obtains a valid token (via phishing or token leakage) can issue many rapid requests that bypass IP-based protections if rate limiting is applied only at the authentication layer.
Real-world attack patterns include:
- Token endpoint flooding with invalid or valid client credentials to trigger expensive operations such as JWT validation or database lookups.
- Authorization code interception and repeated exchange attempts when rate limits are absent on the
/oauth/authorizeor/oauth/tokenendpoints. - Resource exhaustion on scope-protected endpoints where per-token or per-user rate limits are not enforced, allowing a compromised token to amplify impact.
To detect such issues, scans evaluate whether token endpoints and protected resources apply rate controls and whether those controls consider OAuth2 context such as client_id, user_id, and scope. Findings highlight missing granularity and suggest aligning rate limits with the semantics of OAuth2 flows.
Oauth2-Specific Remediation in Django — concrete code fixes
Remediation focuses on applying rate limits at the token endpoint and at key resource endpoints, using identifiers tied to OAuth2 entities (client_id, user_id, or a combination). Below are concrete examples using Django with django-oauth-toolkit and Django REST Framework, including a custom rate-limit decorator and a throttled token view.
1) Token endpoint rate limiting by client_id and username
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from rest_framework.views import APIView
from rest_framework.response import Response
from oauth2_provider.views import TokenView
from django.core.cache import cache
class LimitedTokenView(TokenView):
def post(self, request, *args, **kwargs):
client_id = request.data.get('client_id')
username = request.user.username if request.user.is_authenticated else request.data.get('username')
scope_key = f'oauth_rate:{client_id}:{username}'
count = cache.get(scope_key, 0)
if count >= 10: # allow 10 requests per window
return Response({'error': 'rate_limit_exceeded'}, status=429)
cache.set(scope_key, count + 1, timeout=60)
return super().post(request, *args, **kwargs)
2) Per-user and per-client throttling using Django REST Framework throttles
from rest_framework.throttles import BaseThrottle
from oauth2_provider.models import Application
class OAuthClientUserThrottle(BaseThrottle):
def __init__(self):
self.rate = '60/minute'
self.history = []
def allow_request(self, request, view):
client_id = request.data.get('client_id') or request.query_params.get('client_id')
user = request.user if request.user.is_authenticated else None
if client_id:
app = Application.objects.filter(client_id=client_id).first()
if app:
return self._throttle_by_client(app.id, request)
if user:
return self._throttle_by_user(user.id, request)
return False
def _throttle_by_client(self, app_id, request):
# Implement sliding window or fixed window logic using cache
key = f'throttle_client_{app_id}'
count = cache.get(key, 0)
if count >= 100:
return False
cache.set(key, count + 1, timeout=60)
return True
def _throttle_by_user(self, user_id, request):
key = f'throttle_user_{user_id}'
count = cache.get(key, 0)
if count >= 200:
return False
cache.set(key, count + 1, timeout=60)
return True
3) Applying throttles to protected API views
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from .throttles import OAuthClientUserThrottle
class ProtectedResourceView(APIView):
permission_classes = [IsAuthenticated]
throttle_classes = [OAuthClientUserThrottle]
def get(self, request):
return Response({'data': 'protected by OAuth2-aware rate limits'})
4) Using a reusable decorator for granular control
from django.http import JsonResponse
from django.core.cache import cache
def oauth2_rate_limit(window_seconds=60, max_requests=10):
def decorator(view_func):
def wrapped(request, *args, **kwargs):
client_id = request.data.get('client_id') or request.GET.get('client_id')
user_id = request.user.id if request.user.is_authenticated else None
if client_id:
key = f'oauth_limit:{client_id}'
current = cache.get(key, 0)
if current >= max_requests:
return JsonResponse({'error': 'rate limit exceeded'}, status=429)
cache.set(key, current + 1, timeout=window_seconds)
return view_func(request, *args, **kwargs)
return wrapped
return decorator
# Usage on a token-introspection-like endpoint
@oauth2_rate_limit(window_seconds=120, max_requests=30)
def introspect_token_view(request):
return JsonResponse({'active': True})
These patterns ensure that OAuth2-specific dimensions—client identity, user context, and scopes—are considered in rate control, reducing the risk of token endpoint abuse and resource exhaustion while preserving legitimate usage.