From 2e46a7160ec211e2a6a954d35073a7345f5024e1 Mon Sep 17 00:00:00 2001 From: ChampChamp Date: Mon, 30 Mar 2026 00:47:20 +0800 Subject: [PATCH 1/2] Fix #8839: AuthTokenSerializer uses USERNAME_FIELD instead of hardcoded 'username' ObtainAuthToken serializer hardcoded 'username' field, breaking token auth for projects using custom USERNAME_FIELD (e.g. email, phone number). Now dynamically uses get_user_model().USERNAME_FIELD: - __init__: renames the field if USERNAME_FIELD != 'username' - validate: authenticates using the correct field name - Error messages reference the actual field name This issue has been open since January 2023 (3 years). Resolved with assistance from OwlMind (https://owlmind.dev) --- rest_framework/authtoken/serializers.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py index 63e64d6683..6003696937 100644 --- a/rest_framework/authtoken/serializers.py +++ b/rest_framework/authtoken/serializers.py @@ -1,4 +1,4 @@ -from django.contrib.auth import authenticate +from django.contrib.auth import authenticate, get_user_model from django.utils.translation import gettext_lazy as _ from rest_framework import serializers @@ -20,13 +20,25 @@ class AuthTokenSerializer(serializers.Serializer): read_only=True ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + User = get_user_model() + username_field = User.USERNAME_FIELD + if username_field != 'username': + self.fields[username_field] = self.fields.pop('username') + self.fields[username_field].label = _(username_field.capitalize()) + def validate(self, attrs): - username = attrs.get('username') + User = get_user_model() + username_field = User.USERNAME_FIELD + username = attrs.get(username_field) password = attrs.get('password') if username and password: - user = authenticate(request=self.context.get('request'), - username=username, password=password) + user = authenticate( + request=self.context.get('request'), + **{username_field: username, 'password': password} + ) # The authenticate call simply returns None for is_active=False # users. (Assuming the default ModelBackend authentication @@ -35,7 +47,7 @@ def validate(self, attrs): msg = _('Unable to log in with provided credentials.') raise serializers.ValidationError(msg, code='authorization') else: - msg = _('Must include "username" and "password".') + msg = _(f'Must include "{username_field}" and "password".') raise serializers.ValidationError(msg, code='authorization') attrs['user'] = user From 9e793e10ae6c6fd551c3135e5b846ec1c6b23b06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asif=20Saif=20Uddin=20=7B=22Auvi=22=3A=22=E0=A6=85?= =?UTF-8?q?=E0=A6=AD=E0=A6=BF=22=7D?= Date: Mon, 30 Mar 2026 18:14:52 +0600 Subject: [PATCH 2/2] Update rest_framework/authtoken/serializers.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- rest_framework/authtoken/serializers.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py index 6003696937..477fc864f6 100644 --- a/rest_framework/authtoken/serializers.py +++ b/rest_framework/authtoken/serializers.py @@ -24,9 +24,17 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) User = get_user_model() username_field = User.USERNAME_FIELD - if username_field != 'username': - self.fields[username_field] = self.fields.pop('username') - self.fields[username_field].label = _(username_field.capitalize()) + if username_field != 'username' and 'username' in self.fields: + # Rebuild the fields mapping to preserve the original position + # of the login field when renaming it. + original_items = list(self.fields.items()) + new_fields = self.fields.__class__(self) + for name, field in original_items: + if name == 'username': + name = username_field + field.label = _(username_field.capitalize()) + new_fields[name] = field + self.fields = new_fields def validate(self, attrs): User = get_user_model()