Can Django REST Framework authentication classes fall back after an expired credential raises AuthenticationFailed?
0 reputation · 20 Dec 2020, 14:02 UTC
0 reputation · 20 Dec 2020, 14:02 UTC
When a Django REST Framework view lists multiple authentication classes, the first class that returns a user object sets request.user, while any class that raises AuthenticationFailed immediately stops evaluation. In a least-privilege design, an expired Basic credential or revoked token in an earlier class currently prevents later classes from being tried, because BasicAuthentication and TokenAuthentication raise exceptions rather than returning None. SessionAuthentication behaves differently by returning None when no session user exists, allowing the chain to continue.
Because the core rest_framework.authtoken package has no built-in expiration field, projects must decide how to represent expired credentials and whether to catch backend failures in custom classes. Swallowing exceptions entirely may mask security issues, so the tradeoff between fallback availability and auditability remains unresolved.
Does the sequential evaluation model in DRF 3.x permit a built-in or custom authentication class to return None after an expired credential is detected, allowing a subsequent class to run? What is the recommended pattern for enforcing token expiration without modifying the core authtoken model? How should failures be logged when a custom class intentionally declines to authenticate?
35775 reputation · 20 Dec 2020, 15:35 UTC
AuthenticationFailed. The sequential evaluation model is designed to stop immediately when an exception is raised, signaling that the provided credentials are explicitly invalid or expired.To achieve a fallback mechanism for expired credentials, a custom authentication class must explicitly return None instead of raising an exception. In DRF, the authenticate method logic follows these rules:
(user, auth) tuple: The chain stops, and the user is authenticated.None: The chain continues to the next class in the list.AuthenticationFailed: The chain stops and an error is returned to the client.Since the default rest_framework.authtoken lacks an expiration field, the most robust way to enforce expiration without modifying core models is to subclass the existing authentication class. This allows you to intercept the validation logic and return None when the token is stale.
from rest_framework.authtoken.authentication import TokenAuthentication
from rest_framework.exceptions import AuthenticationFailed
from django.utils import timezone
import logging
logger = logging.getLogger(__name__)
class FlexibleTokenAuthentication(TokenAuthentication):
EXPIRATION_DAYS = 7
def authenticate(self, request):
# Use super to get the token from request
credentials = super().authenticate(request)
if credentials is None:
return None
user, token = credentials
# Logic to check expiration (requires a 'created_at' field
# or a custom field on your token model)
if token.created_at < timezone.now() - timedelta(days=self.EXPIRATION_DAYS):
logger.warning(f"Expired token used for user {user.id}")
# Returning None allows the next class in the list to run
return None
return (user, token)
When you intentionally decline to authenticate by returning None, you should use Django's logging framework to record the event. This ensures that while the API continues to check for other authentication methods (like a session), the system still maintains an audit trail of the expired-credential attempt.
Note: By returning None, the client will receive a generic 401 error only if all authentication classes fail. If you need to explicitly tell the client specifically that their token is expired, you must raise AuthenticationFailed, which will prevent any fallback behavior.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.