I have test function to test whether user can login or not. I am using DRF's APIClient and logging in. When I do a get request to a protected API endpoint I get 401 response.
I tried to print:
print(client.login(username='instructor', password='asdf'))
And it is returning True, which means user is found and logged in.
But still when I do:
response = client.get(reverse('recommend-list'))
I am getting 401.
Here is my test function:
def test_user_authentication(self):
client = APIClient()
user_data = {
'username': 'instructor',
'email': 'myemail@example.com',
'password': 'asdf'
}
client.post(reverse('user-list'), user_data, format='json')
self.assertTrue(User.objects.filter(username='instructor').exists())
print(client.login(username='instructor', password='asdf'))
response = client.get(reverse('recommend-list'))
self.assertEqual(response.status_code, 200)
Here is how I create a new user:
class UserViewSet(viewsets.ViewSet):
def create(self, request):
serializer = UserSerializer(data=request.data)
User = get_user_model()
if serializer.is_valid():
validated_data = serializer.validated_data
try:
user = User.objects.create_user(
username=validated_data['username'],
email=validated_data['email'],
password=validated_data['password']
)
return Response({'id': user.id, 'username': user.username}, status=status.HTTP_201_CREATED)
except IntegrityError:
return Response({'error': 'Username is already taken.'}, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Making get request to here:
class RecommendViewSet(viewsets.GenericViewSet):
authentication_classes = [BasicAuthentication]
permission_classes = [IsAuthenticated]
def list(self, request):
user_id = request.user.id
movies = recommend_movies_for_user(user_id)
return Response({"message": movies}, status=status.HTTP_200_OK)
The user serializer looks like this:
class UserSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
username = serializers.CharField(max_length=150)
email = serializers.EmailField()
password = serializers.CharField(write_only=True)
follower_list = serializers.SerializerMethodField()
following_list = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['id', 'username', 'email', 'password',
'follower_list', 'following_list']
def retrieve(self, instance):
return instance
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
return user
def get_follower_list(self, obj):
follower = obj.followers_set.all()
return FollowSerializer(follower, many=True).data
def get_following_list(self, obj):
following = obj.following_set.all()
return FollowSerializer(following, many=True).data
Alright, I thought it could have been a simple hashing problem, but I was wrong.
The problem is that you are using BasicAuthentication
to authenticate credentials, which tries to find an username and a password that must be present in the request headers
. Thus, you need to encode credentials and properly set the headers
:
class TestUser(TestCase):
def test_user_authentication(self):
client = APIClient()
user_data = {
"username": "instructor",
"email": "myemail@example.com",
"password": "asdf",
}
client.post(reverse("user-list"), user_data, format="json")
get_user_model().objects.get(username="instructor")
# Encode credentials and set the header
cstring = f"{user_data["username"]}:{user_data["password"]}".encode("utf-8")
c=base64.b64encode(cstring)
client.credentials(HTTP_AUTHORIZATION=f"Basic {c.decode()}")
response = client.get(reverse("recommend-list"))
self.assertEqual(response.status_code, 200)
On the other hand, what .login
does is attach an User
to the session with the authenticate
method. Therefore, if you use SessionAuthentication
your original code would work just fine.