I am creating a an API for registering user based on oauth token. My app has functionality like registration and login, adding restaurant etc. I created user registration part but i get error while login. I want login based on token.
I have used django-oauth-toolkit for this and DRF.
What i have done is
Login based on token
class UserCreateAPI(CreateAPIView):
serializer_class = UserCreateSerializer
queryset = User.objects.all()
permission_classes = [AllowAny]
class UserLoginAPI(APIView):
permission_classes = [AllowAny]
serializer_class = UserLoginSerializer
def post(self, request, *args, **kwargs):
access_token = AccessToken.objects.get(token=request.POST.get('access_token'), expires__gt=timezone.now()) # error is shown here
data = request.data
serializer = UserLoginSerializer(data=data)
if serializer.is_valid(raise_exception=True):
new_data = serializer.data
return Response(new_data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
For creating user with a token
class UserCreateSerializer(ModelSerializer):
class Meta:
model = User
extra_kwargs = {"password": {"write_only": True}}
def create(self, validated_data):
username = validated_data['username']
first_name = validated_data['first_name']
last_name = validated_data['last_name']
email = validated_data['email']
password = validated_data['password']
confirm_password = validated_data['password']
user_obj = User(
username = username,
first_name = first_name,
last_name = last_name,
email = email
)
user_obj.set_password(password)
user_obj.save()
if user_obj:
expire_seconds = oauth2_settings.user_settings['ACCESS_TOKEN_EXPIRE_SECONDS']
scopes = oauth2_settings.user_settings['SCOPES']
application = Application.objects.get(name="Foodie")
expires = datetime.now() + timedelta(seconds=expire_seconds)
access_token = AccessToken.objects.create(user=user_obj,
application=application,
token = generate_token(),
expires=expires,
scope=scopes)
return validated_data
class UserLoginSerializer(ModelSerializer):
# token = CharField(allow_blank=True, read_only=True)
username = CharField()
class Meta:
model = User
fields = [
'username',
'password',
# 'token',
]
extra_kwargs = {"password":
{"write_only": True}
}
I have excluded fields part and validation part for shortening the code
Am i doing the right way?
Try to use request.data instead of request.post in order to get the access_token(http://www.django-rest-framework.org/tutorial/2-requests-and-responses/):
access_token = AccessToken.objects.get(token=request.data.get('access_token'), expires__gt=timezone.now())
use request.DATA if you are using the Version-2 of DRF. request.data is for version-3
============UPDATE========================================
You should implement your own login procedure or modify the existing one; because when user log in, the access_token is not actually sent to the server.
The login procedure should look like this :
When the user enter his login and password, your app should send a post request to 127.0.0.1:8000/o/token asking for the token. The request should contains the username, password, client_id and client_secret.
the server then receives the credentials and if they are valid it returns the access_token.
The rest of the time you should query the server using that token.
But, i'm not sure that it is what you are actually doing.
Related
I'm trying to write a unit test for a login endpoint in Django using as much of the built in functionality as possible.
There are existing tests that confirm that the account create endpoint is functioning properly.
In the login view, however, the check_password() function will return True for this test, but the authenticate() function returns None.
Is it safe to use the check_password() function instead?
Otherwise, how do I update this code to use the authenticate() function?
accounts.py
class Account(AbstractUser):
username = models.CharField(max_length=150, unique=True, null=False, default='')
password = models.CharField(max_length=100)
...
REQUIRED_FIELDS = ['username', 'password']
class Meta:
app_label = 'accounts'
db_table = 'accounts_account'
objects = models.Manager()
test_login.py
def test_login(self):
# Create account
request_create = self.factory.post('/accounts/account',
self.request_data,
content_type='application/json')
view = AccountView.as_view()
response_create = view(request_create)
# Login account
request_login = self.factory.post('/accounts/login',
self.request_data,
content_type='application/json')
view = LoginView.as_view()
response = view(request_login)
views.py
class LoginView(View):
def post(self, request):
r = json.loads(request.body)
username = r.get('username')
password = r.get('password')
cp = check_password(password, Account.objects.get(username=username).password)
user = authenticate(username=username, password=password)
P.S. I've checked this thread and is_active is set to true.
It is safe. The main difference is that by using check_password() you are manually checking an User from an authentication backend, thus you have to retrieve the user object and compare its password with the plain text like you do at:
check_password(password, Account.objects.get(username=username).password)
While, with authenticate() it is possible to check credentials against several authentication backends. Meaning that with the former you couldn't hook your application with other authentication sources
You are missing some code in tests.py and views.py. That being said, here is a full test of this LoginView:
tests.py
class TestClientLogin(TestCase):
def setUp(self):
User = get_user_model()
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='test_user',
email='test_user#example.com',
password='super_secret'
)
def test_user_login(self):
request_data = {'username': 'test_user', 'password': 'super_secret'}
request = self.factory.post(
'/accounts/login/',
request_data ,
content_type='application/json'
)
response = LoginView.as_view()(request)
data = json.loads(response.content)
self.assertEqual(self.user.username, data['username'])
self.assertEqual(response.status_code, 200)
views.py
class LoginView(View):
def post(self, request):
data = json.loads(request.body)
username = data.get('username')
password = data.get('password')
user = authenticate(username=username, password=password)
if user is not None:
return JsonResponse({'username': user.username})
else:
return JsonResponse({'username': None})
If you want use as much of the built in functionality as possible:
settings.py:
AUTH_USER_MODEL = 'accounts.Account'
accounts/models.py:
class Account(AbstractUser):
# you dont need to define username or password again
...
REQUIRED_FIELDS = ['username', 'password']
class Meta:
# you don't need to define default app_label, db_table
objects = UserManager() # otherwise don't work "python manage.py createsuperuser"
accounts/views.py:
from django.contrib.auth.views import LoginView
class ClientLogin(LoginView):
pass
accounts/tests.py:
from django.test import TestCase, Client
from django.utils.crypto import get_random_string
class TestClientLogin(TestCase):
def setUp(self):
User = get_user_model()
self.username, self.password = get_random_string(20), get_random_string(20) # don't use constants in tests
self.user = User.objects.create_user(username=self.username, password=self.password )
def test_user_login(self):
request_data = {'username': self.username, 'password': self.password}
response = Client().post('/accounts/login/', request_data)
self.assertEqual(response.status_code, 302)
BUT:
you don't need to test ClientLogin, you don't have any code in view.
The issue i've got is my login endpoint wont permit me to login with authenticated users for unkown reasons
Here is my serializers.py file
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ['id','first_name','last_name','email','is_seller','date_joined']
extra_kwargs ={
'password':{'write_only':True}
}
def create(self, validated_data):
password = validated_data.pop('password', None)
instance = self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
Here is my views.py for Login
class LoginAPIView(APIView):
def post(self, request):
email = request.data['email']
password = request.data['password']
print(email)
print(password)
# since email is unique use first()
user = User.objects.filter(email=email).first()
print(user)
if user is None:
raise exceptions.AuthenticationFailed(f'User {email} not found')
if not user.check_password(password):
raise exceptions.AuthenticationFailed(f'Incorrect Password')
return Response(UserSerializer(user).data)
Now when i do try to login the and given user i get this error below from post man
{
"detail": "Incorrect Password"
}
And these are my logs below
Forbidden: /api/sellers/login/
[15/Jul/2021 21:50:30] "POST /api/sellers/login/ HTTP/1.1" 403 31
I've been unable for hours to figure out why exactly register users can't sign in and also i am using a Custom user model and in my settings.py file the Rest_Framework is set to AllowAny...Thanks in advance for any help...
As Tim Roberts suggested, make sure your fields includes password. Your extra_kwargs for password still requires that password is either a field on the model (in the fields list) or defined as a type of field directly on the Serializer.
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = [
'id',
'first_name',
'last_name',
'email',
'is_seller',
'date_joined',
'password', # <- note added field
]
extra_kwargs = {
'password': {'write_only':True}
}
I am very new to Django Rest Framework and am confused about coding the following task: when a user on the front-end enters an email address, without any other user information, the API should determine whether or not the email already exists. Likewise, when the user enters a username, without any other user information, the API should determine whether or not the username already exists. This is so the user has feedback on whether their email or username is valid before proceeding to the next sign up stage.
I am aware of the existence of validators, but I don't understand how to use them on partial data.
Here is my serializers.py class
class CustomUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True)
username = serializers.CharField(min_length=2)
password = serializers.CharField(min_length=8, write_only=True)
first_name = serializers.CharField(min_length=2)
last_name = serializers.CharField(min_length=2)
class Meta:
model = CustomUser
fields = ('pk', 'email', 'username', 'password', 'created_at', 'first_name', 'last_name')
extra_kwargs = {'password': {'write_only': True}}
def validate_email(self, value):
if CustomUser.objects.filter(email=value).exists():
raise serializers.ValidationError("An account already exists with that email address.")
return value
def validate_username(self, value):
if CustomUser.objects.filter(username=value).exists():
raise serializers.ValidationError("An account aready exists with that username.")
return value
def create(self, validated_data):
password = validated_data.pop('password', None)
instance = self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
And here is my views.py class
class CustomUserCreate(APIView):
permission_classes = (permissions.AllowAny,)
authentication_classes = ()
def post(self, request, format='json'):
"""
Receives an HTTP request. Serializes and saves a user object.
"""
serializer = CustomUserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
json = serializer.data
return Response(json, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class CustomUserGet(APIView):
def get_user(self, username):
try:
return CustomUser.objects.get(username=username)
except CustomUser.DoesNotExist:
raise Http404
def get(self, request, format='json'):
user = self.get_user(request.user.username)
serializer = CustomUserSerializer(user)
return Response(serializer.data)
class LogoutAndBlacklistRefreshTokenForUserView(APIView):
permission_classes = (permissions.AllowAny,)
def post(self, request):
try:
refresh_token = request.data['refresh_token']
token = RefreshToken(refresh_token)
token.blacklist()
return Response(status=status.HTTP_205_RESET_CONTENT)
except:
return Response(status=status.HTTP_400_BAD_REQUEST)
CustomUser is simply an implementation of AbstractUser.
Again, this successfully validates when a user is created with complete data. However, I want to validate on partial data, such as a lone email address and lone username without the presence of other attributes such as first_name, last_name, etc. What is the proper way of approaching this?
I'm using django-rest-auth for API endpoints for my custom user model. Getting user details, I send a GET request to /rest-auth/user/. This works fine with an authenticated user, but is forbidden for an unauthenticated user marked by a 403 Forbidden error.
However, I want other users to be able to view each other's details. How can I change this?
This test is the one which demonstrates this error:
def test_get_user(self):
# /rest-auth/user/ (GET)
# params: username, first_name, last_name
# returns: pk, username, email, first_name, last_name
client = APIClient()
client.login(username=self.user2.username,
password=self.user2.password)
path = "/rest-auth/user/"
user_data = {
"username": self.username,
}
expected_response = {
"pk": self.id,
"username": self.username,
"email": self.email,
"first_name": '',
"last_name": '',
}
response = client.get(path, user_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, expected_response)
EDIT:
I tried to override the permissions of UserDetailsView, yet I failed to do so properly. How do I do this correctly?
from rest_auth import views
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class CustomUserDetailsView(views.UserDetailsView):
permission_classes = (IsAuthenticatedOrReadOnly, )
django-rest-auth /rest-auth/user/ only allow you to get details of the authenticated user.
class UserDetailsView(RetrieveUpdateAPIView):
"""
Reads and updates UserModel fields
Accepts GET, PUT, PATCH methods.
Default accepted fields: username, first_name, last_name
Default display fields: pk, username, email, first_name, last_name
Read-only fields: pk, email
Returns UserModel fields.
"""
serializer_class = UserDetailsSerializer
permission_classes = (IsAuthenticated,)
def get_object(self):
return self.request.user
def get_queryset(self):
"""
Adding this method since it is sometimes called when using
django-rest-swagger
https://github.com/Tivix/django-rest-auth/issues/275
"""
return get_user_model().objects.all()
if you want unauthenticated users to read all user objects you have write your own view.
serializers.py
User = get_user_model()
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
views.py
class UserDetailApiView(generics.RetrieveAPIView):
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
urls.py
path('api/users/<int:pk>',views.UserDetailApiView.as_view(),name='user')
I have used Django Rest Framework for Rest API and django-oauth-toolkit for token based authentication. I have designed and api for user registration. When user is registered, a token is generated and saved to the database. I want user login from that token. I mean a token based authentication because i want to develop a mobile application. I can get access_token using curl when sending request for logging in but how do i implement in view so that app sends a post request to 127.0.0.1:8000/o/token asking for the token so that the request contains username, password, client_id and client_secret. The server then receives the credentials and if they are valid it returns the access_token. The rest of the time it should query the server using that token.
views.py
class UserLoginAPI(APIView):
permission_classes = [AllowAny]
serializer_class = UserLoginSerializer
def post(self, request, *args, **kwargs):
access_token = AccessToken.objects.get(token=request.POST.get('access_token'), expires__gt=timezone.now()) # error is shown here. I get None
data = request.data
serializer = UserLoginSerializer(data=data)
if serializer.is_valid(raise_exception=True):
new_data = serializer.data
return Response(new_data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class UserCreateSerializer(ModelSerializer):
class Meta:
model = User
extra_kwargs = {"password": {"write_only": True}}
def create(self, validated_data):
username = validated_data['username']
first_name = validated_data['first_name']
last_name = validated_data['last_name']
email = validated_data['email']
password = validated_data['password']
confirm_password = validated_data['password']
user_obj = User(
username = username,
first_name = first_name,
last_name = last_name,
email = email
)
user_obj.set_password(password)
user_obj.save()
if user_obj:
expire_seconds = oauth2_settings.user_settings['ACCESS_TOKEN_EXPIRE_SECONDS']
scopes = oauth2_settings.user_settings['SCOPES']
application = Application.objects.get(name="Foodie")
expires = datetime.now() + timedelta(seconds=expire_seconds)
access_token = AccessToken.objects.create(user=user_obj,
application=application,
token = generate_token(),
expires=expires,
scope=scopes)
return validated_data
class UserLoginSerializer(ModelSerializer):
# token = CharField(allow_blank=True, read_only=True)
username = CharField()
class Meta:
model = User
fields = [
'username',
'password',
# 'token',
]
extra_kwargs = {"password":
{"write_only": True}
}
So if you want a api to get token depend on username and password will look like this:
def get_token(request):
username = request.POST.get("username")
password = request.POST.get("password")
.... # other parameters
try:
user = User.objects.get(username=username, password=password)
except ObjectDoesNotExist:
return HttpResponse("Can't find this user")
else:
try:
access_token = AccessToken.objects.get(user=user)
except ObjectDoesNotExist:
return HttpResponse("Haven't set any token")
else:
return HttpResponse(access_token)
If you want to use DRF to handle this:
#api_view(['POST'])
def get_token(request):
# get token by query just like above
serializer = TokenSerializer(data=access_token.token) #you can pass more parameters to data if you want, but you also have to edit your TokenSerializer
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Your TokenSerializer:
class TokenSerializer(ModelSerializer):
class Meta:
model = AccessToken
field = (token,)
EDIT
It depends
Web, you post your username and password to login api, your browser store session in your cookies.
Mobile, you post your username and password to login api, server response the token then you store it in your mobile, maybe keychain when you are developing IOS app.And send it as http header when you want to access the server, how-can-i-get-all-the-request-headers-in-django