William
William

Reputation: 23

Django Rest Framework TokenAuthentication not working "Invalid Token"

I'm successfully creating a token upon a user's login (using a CustomUser model that replaces the username field with email), but when using this token in subsequent requests, the response is "Invalid token". Below are excerpts from the relevant files:

settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users',
    'goals',
    'rest_framework',
    'rest_framework.authtoken',
]
...

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
}

Views to register and login users in users/views.py

class UserRegistrationView(generics.CreateAPIView):
    serializer_class = UserSerializer
    permission_classes = [permissions.AllowAny]

class UserLoginView(APIView):
    def post(self, request):
        user = authenticate(username=request.data['email'], password=request.data['password'])
        if user:
            token, created = Token.objects.get_or_create(user=user)
            return Response({'token': token.key})
        else:
            return Response({'error': 'Invalid credentials'}, status=401)

The view I'm testing in goals/views.py:

@api_view(['POST'])
@permission_classes((IsAuthenticated, ))
def create_topic(request):
    data = JSONParser().parse(request)
    serializer = TopicSerializer(data=data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=201)
    else:
        return Response(serializer.errors, status=400)

The test that fails in goals/tests.py

class SerializerTests(TestCase):

    def setUp(self):
        email = "[email protected]"
        pw = "foo"
        self.user = CustomUser.objects.create_user(email=email, password=pw)
        self.user_data = {'email':email, 'password':pw}
        login_response = self.client.post('/users/login/', data=json.dumps(self.user_data), content_type="application/json")
        print(login_response.data)
        self.token = login_response.data['token']
        self.headers = {'Authorization': 'Token {self.token}'}

    def test_create_topic(self):
        self.topic = {'name':'topic_1', 'created_by':self.user.pk}
        response = self.client.post('/goals/topic/create/', data=json.dumps(self.topic), content_type="application/json", headers=self.headers) #should this be replaced by some DRF components?
        self.assertEqual(201, response.status_code)

Unfortunately response.data simply states {'detail': ErrorDetail(string='Invalid token.', code='authentication_failed')}. This does not appear to be an issue with the header, since changing the header format yields an error message saying no authentication credentials were provided. So it seems the token is submitted succesfully, yet somehow it is not accepted. I have checked and confirmed that the token created upon login is saved properly so it should be available for the requests - still it doesn't work...

What am I doing wrong here?

Upvotes: 1

Views: 60

Answers (2)

Michał
Michał

Reputation: 503

While the @Antoliny's answer is true, it might not be enough.

In your settings, I can't see AUTH_USER_MODEL setting. If you use your own, custom user model, you should set:

# settings.py

AUTH_USER_MODEL = "users.CustomUser"  # Use real path for your model.

Docs: https://docs.djangoproject.com/en/5.1/topics/auth/customizing/#substituting-a-custom-user-model

It's needed for Django and all other packages to treat this model as a default for user model. Token model from rest_framework.authtoken uses this setting for token's relation to user.

Upvotes: 0

Antoliny Lee
Antoliny Lee

Reputation: 571

It's a simple issue.

class SerializerTests(TestCase):

    def setUp(self):
        email = "[email protected]"
        pw = "foo"
        self.user = CustomUser.objects.create_user(email=email, password=pw)
        self.user_data = {'email':email, 'password':pw}
        login_response = self.client.post('/users/login/', data=json.dumps(self.user_data), content_type="application/json")
        print(login_response.data)
        self.token = login_response.data['token']
        self.headers = {'Authorization': 'Token {self.token}'}
        ...

If you look at the SerializerTests setUp method, you set the header through the token you received.

self.headers = {'Authorization': 'Token {self.token}'}

However, the above sentence does not apply formatting, so self.token is applied as a string rather than the value of the self.token variable.

For self.token to be applied as a variable value, I used f-string in that code.

self.headers = {'Authorization': f'Token {self.token}'}

Upvotes: 1

Related Questions