Broken Authentication in Django with Bearer Tokens
Broken Authentication in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Django does not include a built-in Bearer token implementation for API authentication; developers typically add token handling using packages such as djangorestframework-simplejwt or custom middleware. When Bearer tokens are used incorrectly, the authentication boundary that should protect endpoints breaks down, leading to Broken Authentication. Several patterns commonly weaken the security of token-based flows in Django.
One vulnerability pattern is weak or predictable token generation. If tokens are derived from low-entropy values (e.g., sequential user IDs or timestamps), attackers can guess valid tokens and assume identities of other users without possessing a legitimate token. In Django, this often occurs when custom token generators omit cryptographic randomness or rely solely on user attributes such as username or email.
A second pattern is insufficient transport protection. Bearer tokens must be transmitted exclusively over HTTPS. Without enforced TLS, tokens can be intercepted in transit via passive sniffing or active man-in-the-middle attacks. In development, forgetting to set settings.DEBUG = False and omitting HTTP Strict Transport Security (HSTS) can inadvertently allow tokens to be sent over HTTP, exposing them to interception.
Third, improper token storage on the client side leads to exposure. Bearer tokens stored in local storage are accessible to JavaScript and vulnerable to cross-site scripting (XSS). If a Django application serves API responses to JavaScript clients without appropriate Content Security Policy (CSP) and relies on tokens in local storage, a reflected or stored XSS flaw can result in token theft and unauthorized API access.
Misconfigured token validation is another contributor. In Django, token verification should include checks such as token expiration, audience, and issuer claims (for JWTs). Skipping these checks or accepting unsigned tokens (e.g., algorithm=['none'] in some JWT libraries) allows attackers to forge tokens. Similarly, using a weak signing algorithm like HS256 with a shared secret that is hardcoded or exposed in client-side code weakens integrity.
Additionally, missing or weak rate limiting on token validation endpoints enables brute-force and credential-stuffing attacks against token introspection or login endpoints. Without per-client or per-IP rate limits, an attacker can systematically test stolen or guessed tokens to discover valid ones. In Django REST Framework, omitting throttle classes on views that verify tokens leaves these endpoints open to automated abuse.
Finally, token revocation and logout handling are often incomplete. Bearer tokens are typically stateless; if a compromised token remains valid until natural expiration and there is no mechanism to revoke it (e.g., a denylist or short-lived tokens with refresh rotation), an attacker can continue using a stolen token. In Django, failing to implement token blacklisting or short expirations increases the window of exposure for Broken Authentication.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
Remediation focuses on strengthening token generation, transport, storage, validation, rate limiting, and revocation. Below are concrete practices and code examples for Django projects using Bearer tokens.
1. Use cryptographically strong token generation
Ensure tokens are generated with sufficient entropy. For custom token models, use secrets.token_urlsafe instead of predictable values.
import secrets
from django.db import models
class ApiToken(models.Model):
user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
token = models.CharField(max_length=255, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
def save(self, *args, **kwargs):
if not self.token:
self.token = secrets.token_urlsafe(32) # 256 bits of entropy
super().save(*args, **kwargs)
2. Enforce HTTPS in production
Require HTTPS for all API traffic and set HSTS headers. In settings, configure secure transport for production environments.
# settings.py
if not DEBUG:
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
3. Store tokens securely on the client
Serve API clients with strong CSP and avoid storing Bearer tokens in local storage. For SPAs, prefer short-lived tokens in memory and use secure, HTTP-only cookies for refresh tokens when possible.
4. Validate token claims rigorously
If using JWTs, validate expiration (exp), audience (aud), and issuer (iss). Do not accept unsigned tokens.
# Example using PyJWT with strict validation
import jwt
from django.conf import settings
def decode_token(encoded_token):
try:
payload = jwt.decode(
encoded_token,
settings.SECRET_KEY,
algorithms=['HS256'],
audience='myapi',
issuer='django-auth',
)
return payload
except jwt.ExpiredSignatureError:
raise ValueError('Token expired')
except jwt.InvalidTokenError:
raise ValueError('Invalid token')
5. Apply rate limiting to token-related endpoints
Use Django REST Framework throttling to protect token validation and login endpoints.
# throttles.py
from rest_framework.throttling import AnonRateThrottle
class TokenAnonRateThrottle(AnonRateThrottle):
rate = '100/day;10/hour'
# views.py
from rest_framework.throttling import UserRateThrottle
from .throttles import TokenAnonRateThrottle
class TokenVerifyView(APIView):
throttle_classes = [TokenAnonRateThrottle, UserRateThrottle]
def post(self, request):
token = request.data.get('token')
# validate token logic
return Response({'valid': True})
6. Implement token revocation and short lifetimes
Use short expiration times for access tokens and maintain a denylist for revoked tokens. Rotate refresh tokens and bind tokens to client context where feasible.
# models.py
class RevokedToken(models.Model):
token_id = models.CharField(max_length=255, unique=True)
revoked_at = models.DateTimeField(auto_now_add=True)
# utils.py
def is_token_revoked(token_id):
return RevokedToken.objects.filter(token_id=token_id).exists()
# In token verification flow, check revocation
if is_token_revoked(payload.get('jti')):
raise ValueError('Token revoked')
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |