Jwt Misconfiguration in Django with Mutual Tls
Jwt Misconfiguration in Django with Mutual Tls — how this specific combination creates or exposes the vulnerability
JWT misconfiguration in Django becomes particularly risky when combined with Mutual TLS (mTLS), because mTLS enforces client certificate authentication at the transport layer while developers may assume this also secures the JWT layer. This false sense of security can lead to missing or weak JWT validation, such as not verifying signatures, skipping issuer/audience checks, or accepting unsigned tokens.
When mTLS is used, requests may already present a valid client certificate, and the application might incorrectly trust the JWT payload without proper verification. For example, if django-oauth-toolkit or a custom JWT middleware does not explicitly validate the alg header and rejects none algorithms, an attacker could present a valid mTLS client certificate and supply a JWT with alg: none to gain unauthorized access.
Another scenario involves mismatched token audiences. mTLS ensures the client is known to the server, but if the JWT audience (aud) claim is not checked against the intended API consumers, tokens issued for one service may be accepted by another backend behind the same mTLS gateway. This can lead to horizontal or vertical privilege escalation when roles or scopes are not enforced consistently.
The interplay also surfaces issues in token binding. mTLS provides channel-level identity, but if the application does not correlate the certificate subject or serial with the JWT subject or scopes, there is no guarantee that the token matches the authenticated client certificate. Without this binding, a stolen JWT could be used from a different mTLS-authenticated session if the token itself is not tightly scoped and validated.
Additionally, weak or missing token expiration checks combined with long-lived certificates in mTLS can keep compromised tokens valid for extended periods. Attackers may capture JWTs from logs or error messages, and if signature verification or iss/aud/exp/nbf validation is incomplete, they can reuse them across requests that are accepted due to the mTLS context.
Finally, improper integration between mTLS and JWT can lead to bypasses where optional token validation is allowed. If Django views or permission classes treat JWT as optional when a client certificate is present, attackers can omit the JWT entirely and rely solely on the certificate, or supply a malformed token and still proceed if the certificate check passes.
Mutual Tls-Specific Remediation in Django — concrete code fixes
Remediation centers on strict JWT validation regardless of mTLS presence, binding token claims to the certificate identity, and enforcing least privilege. Always validate signature, issuer, audience, and standard claims, and never skip token checks when a client certificate is provided.
Example 1: Strict JWT validation middleware with mTLS awareness
import jwt
from django.http import HttpResponseForbidden
from django.utils.deprecation import MiddlewareMixin
class JwtMtlsValidationMiddleware(MiddlewareMixin):
# Public keys or JWKS endpoint for your issuer
JWKS_URL = 'https://auth.example.com/.well-known/jwks.json'
AUDIENCE = 'my-api.example.com'
ISSUER = 'https://auth.example.com/'
def process_request(self, request):
# mTLS already verified client cert at gateway; ensure JWT is present and valid
auth = request.META.get('HTTP_AUTHORIZATION', '')
if not auth.lower().startswith('bearer '):
return HttpResponseForbidden('Missing Bearer token')
token = auth[7:].strip()
if not token:
return HttpResponseForbidden('Empty token')
# Fetch signing keys (in practice cache JWKS)
import jwt
from jwt import PyJWKClient
jwks_client = PyJWKClient(self.JWKS_URL)
signing_key = jwks_client.get_signing_key_from_jwt(token)
# Enforce expected claims and algorithms
try:
decoded = jwt.decode(
token,
signing_key.key,
algorithms=['RS256'],
audience=self.AUDIENCE,
issuer=self.ISSUER,
options={'require_exp': True, 'verify_exp': True}
)
# Bind identity: ensure JWT subject matches certificate subject if available
# e.g., request.META.get('SSL_CLIENT_SUBJECT') or request.META.get('SSL_CLIENT_I_DN')
request.user = self._map_to_user(decoded)
except jwt.InvalidTokenError as e:
return HttpResponseForbidden(f'Invalid token: {str(e)}')
def _map_to_user(self, decoded):
# Map claims to Django user, applying least privilege
from django.contrib.auth.models import User
username = decoded.get('sub') or decoded.get('preferred_username')
if not username:
raise ValueError('Missing subject in token')
user, _ = User.objects.get_or_create(username=username)
# Apply scopes/roles from token to Django permissions or groups
return user
Example 2: View-level enforcement with scope/role checks
from django.http import JsonResponse
from django.views import View
from functools import wraps
from django.core.exceptions import PermissionDenied
def require_scope(required_scope):
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
if not hasattr(request, 'user') or request.user.is_anonymous:
raise PermissionDenied('Authentication required')
# scopes may be mapped to groups or custom attributes
token_scopes = getattr(request, 'jwt_scopes', set())
if required_scope not in token_scopes:
raise PermissionDenied(f'Missing scope: {required_scope}')
return view_func(request, *args, **kwargs)
return _wrapped_view
return decorator
class SensitiveDataView(View):
@require_scope('data:read:sensitive')
def get(self, request):
return JsonResponse({'data': 'protected information'})
Example 3: Enforce algorithm and reject none
import jwt
from django.conf import settings
def decode_token_hard(token):
# Always specify allowed algorithms; never use 'none' or 'auto'
algorithms = ['RS256', 'ES256']
# If you use asymmetric keys, load the public key from a trusted source
public_key = settings.JWT_PUBLIC_KEY # PEM string or loaded from JWKS
try:
payload = jwt.decode(
token,
public_key,
algorithms=algorithms,
audience='my-api.example.com',
issuer='https://auth.example.com/',
options={'verify_signature': True, 'require_exp': True}
)
return payload
except jwt.InvalidAlgorithmError:
raise ValueError('Algorithm not allowed')
except jwt.InvalidTokenError:
raise ValueError('Invalid token')
Operational and deployment guidance
- Configure your API gateway or mTLS frontend to pass the verified client certificate metadata (subject, issuer, serial) in headers for downstream validation, but never rely on them alone.
- Rotate JWT signing keys and certificate materials regularly and map tokens to short lifetimes; mTLS long-lived certs should not imply long-lived tokens.
- Use centralized JWKS and strict CORS and referrer policies; log validation failures and certificate binding mismatches for audit without exposing sensitive details.
- Test the combination: simulate tokens with missing claims, wrong audience, and unsupported algorithms while mTLS is present to ensure validation fails securely.
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 |