I have created a login screen which uses a username and password to login. I have a jwt authentication but I'm getting a bit confused because I have two login urls and I want only one. The jwt url is providing me the token meanwhile the other one that I have created myself I can login but no token is getting generated. This is my code:
serializers.py
class UserLoginSerializer(ModelSerializer):
token = CharField(allow_blank=True, read_only=True)
username = CharField(required=False, allow_blank=True)
class Meta:
model = User
fields = [
'username',
'password',
'token',
]
extra_kwargs = {"password":{"write_only": True}}
def validate(self, data):
user = authenticate(**data)
if user:
if user.is_active:
data['user'] = user
return data
raise exceptions.AuthenticationFailed('Account is not activated')
raise exceptions.AuthenticationFailed('User is not active')
def validate(self, data):
user_obj = None
username = data.get("username", None)
password = data["password"]
if not username:
raise ValidationError("A username is required")
user = User.objects.filter(
Q(username=username)
).distinct()
if user.exists() and user.count() == 1:
user_obj = user.first()
else:
raise ValidationError("This username is not valid")
if user_obj:
if not user_obj.check_password(password):
raise ValidationError("Incorrect credentials, please try again")
data["token"] = "SOME RANDOM TOKEN"
return data
views.py
class UserLoginAPIView(APIView):
permission_classes = [AllowAny]
serializer_class = UserLoginSerializer
def post(self, request, *args, **kwargs):
data = request.data
serializer = UserLoginSerializer(data=data)
if serializer.is_valid(raise_exception=True):
new_data = serializer.data
return Response(new_data, status=HTTP_200_OK)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
You can re-write your login serializer like this:
from rest_framework_jwt.serializers import JSONWebTokenSerializer
class SignInJWTSerializer(JSONWebTokenSerializer):
def validate(self, attrs):
email = attrs.get('email')
password = attrs.get('password')
if email is None or password is None:
message = 'Must include email and password'
raise serializers.ValidationError({'message': message})
...
And in url:
from rest_framework_jwt.views import ObtainJSONWebToken
path('login/', ObtainJSONWebToken.as_view(serializer_class=serializers.SignInJWTSerializer), name='login'),
also remove view class
Related
I have a Django REST App where I want to do the log in. I need the id of the currend logged in user and when I print it, it returns None. This is my code:
serializer.py
class LoginSerializer(serializers.Serializer):
username = serializers.CharField(
label="Username",
write_only=True
)
password = serializers.CharField(
label="Password",
# This will be used when the DRF browsable API is enabled
style={'input_type': 'password'},
trim_whitespace=False,
write_only=True
)
view.py
class LoginAPI(APIView):
def post(self, request):
username = request.data['username']
password = request.data['password']
user = User.objects.filter(username=username).first()
if user is None:
raise AuthenticationFailed('User not found!')
if not user.check_password(password):
raise AuthenticationFailed('Incorrect password!')
payload = {
'id': user.id,
}
token = jwt.encode(payload, 'secret', algorithm='HS256').decode('utf-8')
response = Response()
response.set_cookie(key='token', value=token, httponly=True)
response.data = {
'token': token
}
print(self.request.user.id)
return response
I don't know what I have to change in my log in view in order to print the id, not None.
You didn't login yet in that post function. So self.request.user does not exist.
print(user.id)
I´m trying to get response that include name apart from token.
In the documentation https://www.django-rest-framework.org/api-guide/authentication/ about ObtainAuthToken I see we can overwrite the post fn but I´m not sure how to apply it in my CreateTokenView class.
serializers.py
class AuthTokenSerializer(serializers.Serializer):
"""Serializer for the user authentication object"""
email = serializers.CharField()
password = serializers.CharField(
style={'input_type': 'password'},
trim_whitespace=False
)
def validate(self,attrs):
"""Overwriting the validate() fn"""
email = attrs.get('email')
password = attrs.get('password')
user = authenticate(
request=self.context.get('request'),
username=email,
password=password
)
if not user:
msg = _('Unable to authenticate with provided credentials')
raise serializers.ValidationError(msg, code='authentication')
attrs['user'] = user
return attrs
views.py
from rest_framework.authtoken.views import ObtainAuthToken
class CreateTokenView(ObtainAuthToken):
"""Create a new auth token for user"""
serializer_class = AuthTokenSerializer
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
Add the post fn and customize the kwargs
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
class CreateTokenView(ObtainAuthToken):
"""Create a new auth token for user"""
serializer_class = AuthTokenSerializer
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'name': user.name
})
I am using knox token in authentication so Here's my Signup and Signin view, How to test the signing view with APIRequestFactory
class SignUpAPI(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
token = AuthToken.objects.create(user)
return Response({"user": UserSerializer(user, context=self.get_serializer_context()).data})
class SignInAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request):
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],
}
)
the endpoints
path("auth/register", SignUpAPI.as_view(), name="RegisterView"),
path("auth/login", SignInAPI.as_view(), name="LoginView"),```
Assuming you work with DRF, you could try something like this:
from rest_framework.test import APIClient
from knox.serializers import User
class SignInAPITests(TestCase):
def test_sign_in(self):
self.user = User.objects.create_user(
username='test_user',
password='12345')
self.client = APIClient()
response = self.client.post(auth_url,
data=dict(username="test_user",
password="12345"))
access_token = res.data["token"]
self.assertIsNotNone(access_token)
ViewSet
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
# Generate user token
_, token = AuthToken.objects.create(user)
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token
})
As in the above viewset i've added custom field token added as a custom field manually. But it's not showing in swagger docs.
What I've tried
from rest_framework import status
from drf_yasg import openapi
from authprofile.serializers import UserSerializer
login_response_schema = {
status.HTTP_201_CREATED: openapi.Response(
description="User Created",
examples= {
"application/json":{
"user":UserSerializer,
"token": "<Token>",
}
}
)
}
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
#swagger_auto_schema(operation_description="Login with email and password", \
responses=login_response_schema)
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
# Generate user token
_, token = AuthToken.objects.create(user)
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token
})
Doing so gives me error saying:
TypeError: Object of type 'SerializerMetaclass' is not JSON serializable
Code in serializers.py file
class LoginSerializer(serializers.Serializer):
email = serializers.EmailField(required=True)
password = serializers.CharField(write_only=True, required=True)
def validate(self, attrs):
# Get required info for validation
email = attrs['email']
password = attrs['password']
"""
Check that the email is available in the User table
"""
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
raise serializers.ValidationError({"email": 'Details do not match an active account'})
if not user.check_password(password):
raise serializers.ValidationError({"password": 'Your password is incorrect'})
return attrs
The below will be used to format the response sample shown in swagger
class LoginResponseSerializer200(serializers.Serializer):
user = UserSerializer()
tokens = JWTTokenResponseSerializer()
Code in views.py file
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
#swagger_auto_schema(
request_body=serializers.LoginSerializer,
responses={
200: LoginResponseSerializer200
}
)
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
# Generate user token
_, token = AuthToken.objects.create(user)
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token
})
AssertionError: 'LoginView' should either include a serializer_class attribute, or override the get_serializer_class() method
Here is the code
class LoginView(GenericAPIView):
def post(self, request):
data = request.data
username = data.get('username', '')
password = data.get('password', '')
user = auth.authenticate(username=username, password=password)
if user:
auth_token = jwt.encode({'username': user.username}, settings.JWT_SECRET_KEY)
serializer = UserSerializer(user)
data = {'user': serializer.data, 'token': auth_token}
return Response(data, status=status.HTTP_200_OK)
return Response({'detail': 'Invalid credential'}, status=status.HTTP_401_UNAUTHORIZED)