I have created a basic app using Django's built in authentication system. I successfully created a User object in the shell using
>>python manage.py createsuperuser.
I then created a basic view, 'UserLogin' along with corresponding serializers/urls, to log an existing user in using the django.contrib.auth authenticate(), and login() functions. Upon testing with the credentials of my created user, the login function seemed to have worked successfully.
To test this, I created another view function, 'CurrentUser' which returns the username of the currently logged in user. However, this view returns the user as empty.
Why would the 'CurrentUser' view be returning no user as logged in? I have attached my code (minus imports) below.
views.py:
class UserLogin(APIView):
def post(self, request, format = None):
serializer = UserLoginSerializer(data=request.data)
if serializer.is_valid():
user = authenticate(username=serializer.validated_data["username"], password=serializer.validated_data["password"])
if user is not None:
login(request, user)
return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED)
return Response("Invalid username/password", status=status.HTTP_401_UNAUTHORIZED)
return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
class CurrentUser(APIView):
def get(self, request, format = None):
return Response(self.request.user.username)
serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username']
class UserLoginSerializer(serializers.Serializer):
username = serializers.CharField(max_length=300, required=True)
password = serializers.CharField(required=True, write_only=True)
urls.py:
urlpatterns = [
path('login/', views.UserLogin.as_view()),
path('current/', views.CurrentUser.as_view())
]
Any guidance would be much appreciated.
Thanks
A cause for this could be a missing SessionMiddleware:
/login view works/current isn't bound to the authenticated sessionCan you double check that settings.MIDDLEWARES contain django.contrib.sessions.middleware.SessionMiddleware?
You were trying to test the authentication in the wrong way. This is API, which is stateless. That is Django doesn't have any idea about the previous request that you have made to the UserLogin.
So, you need to attach the token and session-id (or something that can be used to identify the user) with the "second request".
Since you are using DRF, you don't have to write your own authentication mechanisms. You can use DRF's BasicAuthentication (or any other authentication scheme) to authenticate.
So, change your view as
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated
class CurrentUser(APIView):
authentication_classes = (BasicAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):
return Response({
"user": {
"id": request.user.id,
"username": request.user.username
}})
Execution using requests
In [5]: import requests
In [6]: from requests import auth
In [7]: response = requests.get(url=url, auth=auth.HTTPBasicAuth("admin", "passme123@#$"))
In [8]: print(response.json())
{'user': {'id': 1, 'username': 'admin'}}
Since you are using DRF, you can make use of any of these DRF authentications schemes in your code-base
You have to set the default auth class as session authenticate class in DRF settings. Read more about it here [1].
Session auth uses session id to identify the user. So you have to send the cookie based session id in the request. Read about session auth here [2].
for example:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication', # <-- set this class
]
}
Use this code:
def post(self, request, format = None):
serializer = UserLoginSerializer(data=request.data)
if serializer.is_valid():
user = authenticate(username=serializer.validated_data["username"], password=serializer.validated_data["password"])
if user:
return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED)
return Response("Invalid username/password", status=status.HTTP_401_UNAUTHORIZED)
return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
But my recommendation is to use Token auth [3].
To use token auth 2 things will change:
Token <token-value>Your post method and API views code will remain same.
[1] https://www.django-rest-framework.org/api-guide/authentication/#setting-the-authentication-scheme
[2] https://www.django-rest-framework.org/api-guide/authentication/#sessionauthentication
[3] https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
Since you are using Django rest framework, i m assuming you are developing rest apis. Rest Apis by design are meant to be stateless meaning no two requests will be dependent on each other.
What this essentially means is you have to authenticate every request and set the user in request after authentication.
There are many ways of doing it like many have suggested to use basicAuth, or token based authentication. All of which can we easily implemented by reading documentation Auth Doc
The out of the box django views uses cookies to identify logged in user. Which in this case is not helpful.
I am using DRF for login. You can check this serializers and login view.
serializers.py:
class LoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, data):
user = authenticate(**data)
if user and user.is_active:
return user
raise serializers.ValidationError("Incorrect Credentials")
login view:
class LoginAPIView(generics.GenericAPIView):
queryset = User.objects.all()
permission_classes = [AllowAny]
serializer_class = LoginSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1]
})
settings.py rest framework classes :
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
# 'rest_framework.authentication.BasicAuthentication',
# 'rest_framework.authentication.SessionAuthentication',
'knox.auth.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
You have to attach an authentication token and session-id for your purpose. I wish this document would helpful for you. I recommend you to use Token Auth2. https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication