Jwt Misconfiguration in Django with Basic Auth
Jwt Misconfiguration in Django with Basic Auth — how this specific combination creates or exposes the vulnerability
Using JSON Web Tokens (JWT) alongside HTTP Basic Authentication in Django can unintentionally weaken authentication and authorization when configurations are incorrect. A common misconfiguration is accepting a JWT without enforcing strict token validation while also allowing Basic Auth credentials to be treated as a primary or fallback authentication mechanism. In such setups, an API endpoint might trust a JWT for permissions but still process Basic Auth headers, which can lead to confused deputy or authorization bypass scenarios.
For example, if JWT verification is lenient (e.g., not validating signature rigorously, not checking the iss or aud claims, or using a weak algorithm like none), an attacker who intercepts or guesses a Basic Auth credential pair (username:password) could potentially use those credentials to obtain or forge a JWT. Additionally, if Django’s authentication backend chain includes both Basic Auth and JWT handlers without proper precedence and isolation, an attacker may escalate privileges by leveraging a low-privilege Basic Auth token to access endpoints that rely on JWT claims for authorization checks.
Another specific risk arises when JWTs embed user roles or permissions but the Django permission system still relies on Basic Auth–based session or token checks. If the JWT claims are not cross-validated against the authenticated user derived from Basic Auth, an attacker could manipulate token contents (if unsigned or weakly signed) and gain unauthorized access to admin or sensitive endpoints. This becomes critical in APIs where unauthenticated or weakly authenticated probing is possible, as scanners can enumerate endpoints that accept Basic Auth and then attempt to inject or swap JWTs to test for missing authorization checks.
The combination also increases exposure to token leakage via logs or error messages when Basic Auth credentials are sent in headers repeatedly, and JWTs are used intermittently. Misconfigured CORS or missing HTTPS further amplifies the risk, as Basic Auth credentials are base64-encoded (not encrypted) and JWTs can be stolen through insecure transport. OWASP API Security Top 10 categories such as Broken Object Level Authorization (BOLA) and Broken Authentication are often relevant when these two mechanisms are inconsistently enforced.
Proper security requires clear boundaries: either rely on JWTs with strict validation (signature, expiry, issuer, audience, and scope) or use Basic Auth over TLS with strong password policies and additional protections like rate limiting. If both are used, enforce a strict authentication precedence, validate JWTs on every request, and ensure permissions derived from Basic Auth are not bypassed by JWT claims.
Basic Auth-Specific Remediation in Django — concrete code fixes
To secure Django APIs that historically used or currently use Basic Auth, implement explicit authentication backends, enforce HTTPS, and avoid mixing schemes without clear precedence. Below are concrete code examples demonstrating secure practices.
1. Use HTTPS and reject non-secure requests
Ensure your API rejects HTTP requests in production by enforcing secure connections.
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
2. Define a strict Basic Auth backend with hashers
Use Django’s built-in RemoteUserMiddleware and RemoteUserBackend only if you integrate with an external source (e.g., an SSO or reverse proxy). For custom Basic Auth handling, implement a dedicated backend with strong password hashing.
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.hashers import check_password, make_password
from .models import AppUser
class BasicAuthBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
try:
user = AppUser.objects.get(username=username)
if user.check_password(password):
return user
except AppUser.DoesNotExist:
return None
return None
def get_user(self, user_id):
try:
return AppUser.objects.get(pk=user_id)
except AppUser.DoesNotExist:
return None
3. Explicitly configure authentication classes in views
Do not rely on global settings that may enable Basic Auth by default. Instead, specify authentication per view or viewset.
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import BasicAuthentication
class SecureEndpoint(APIView):
permission_classes = [IsAuthenticated]
authentication_classes = [BasicAuthentication]
def get(self, request):
return Response({'message': 'Authenticated with Basic Auth', 'user': request.user.username})
4. Prefer token-based or JWT authentication with strict validation
If you move away from Basic Auth, use token-based schemes and validate JWTs rigorously. Below is an example using Django with PyJWT and a custom permission class.
import jwt
from django.conf import settings
from rest_framework.permissions import BasePermission
class JWTBearerAuthentication:
def authenticate(self, request):
auth_header = request.headers.get('Authorization')
if auth_header and auth_header.startswith('Bearer '):
token = auth_header.split(' ')[1]
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'], audience='api', issuer='myapp')
user_id = payload.get('sub')
user = User.objects.get(id=user_id)
return (user, token)
except jwt.ExpiredSignatureError:
raise AuthenticationFailed('Token expired')
except jwt.InvalidTokenError:
raise AuthenticationFailed('Invalid token')
return None
class HasScopePermission(BasePermission):
def __init__(self, scopes):
self.scopes = scopes
def has_permission(self, request, view):
if request.user and request.auth:
token = request.auth[1]
payload = jwt.decode(token, settings.SECRET_KEY, options={'verify_signature': True}, algorithms=['HS256'])
token_scopes = payload.get('scope', '').split()
return all(s in token_scopes for s in self.scopes)
return False
5. Remove Basic Auth from JWT-secured endpoints
If JWTs must be used, disable Basic Auth for those views to prevent confusion and reduce attack surface.
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
class JWTOnlyEndpoint(APIView):
permission_classes = [IsAuthenticated]
authentication_classes = [TokenAuthentication]
def get(self, request):
return Response({'message': 'JWT or session-based auth only'})
By explicitly defining authentication classes, validating tokens, and isolating schemes, you reduce the risk of confused deputy issues and ensure that permissions are evaluated consistently.
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 |