So I have a view that sends a confirmation email to the new user to activate with a uid and token. That part works fine. When the user clicks on the link it's suppose to send a PUT to my ActivateUserAPI where it grabs the 'uid' and 'token' from the url and sets email_confirmed = True, but it is not getting past djangos check_token function
url:
path('api/auth/activate/<path:uid>/<path:token>',
ActivateUserAPI.as_view(), name='activate'),
tokens.py
from django.contrib.auth.tokens import PasswordResetTokenGenerator
import six
class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp):
return (six.text_type(user.pk) + six.text_type(timestamp)) + six.text_type(user.email)
account_activation_token = AccountActivationTokenGenerator()
serializer:
class ActivationSerializer(serializers.ModelSerializer)
class Meta:
model = User
fields = ('id', 'email_confirmed',)
view:
class ActivateUserAPI(generics.UpdateAPIView):
serializer_class = ActivationSerializer
permission_classes = [
permissions.AllowAny
]
def put(self, request, *args, **kwargs):
token = self.kwargs['token']
try:
uid = force_str(urlsafe_base64_decode(self.kwargs['uid']))
user = User.objects.get(pk=uid)
print(account_activation_token.check_token(user, token)) # **this always returns False**
except(TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
if user is not None and account_activation_token.check_token(user, token):
serializer = ActivationSerializer(user, data=request.data)
if serializer.is_valid():
user.email_confirmed = True
user.save()
return Response(serializer.data)
return HttpResponse('Thank you for your email confirmation. Now you can login your account.')
else:
return HttpResponse('Activation link is invalid!')
The weird thing is that if I access the api directly through the url it works fine, like in the link below
View of DRF
Related
I have django app with authentication in it and email verification. When user is created activation email is sent with link inside of it, when user clicks this link is doesn't take him nowhere.
views.py
class customer_register(CreateView):
model = User
form_class = CustomerSignUpForm
template_name = 'authentication/customer_register.html'
def form_valid(self, form):
user = form.save()
user.token = str(uuid.uuid4())
subject = 'Verify your account | Zane'
message = f"http://127.0.0.1:8000/accounts/verify/{user.token}/"
recipient_list = [user.email]
send_mail(
subject,
message,
'from#example.com',
['to#example.com'],
fail_silently=False,
)
return redirect('/')
def activate(request, token):
try:
obj = models.User.objects.get(email_token = token)
obj.signup_confirmation = True
obj.save()
return HttpResponse('Your account is verified')
except Exception as e:
return HttpResponse('Invalid token')
urls.py
path('verify/<uuid:pk>/', views.activate, name='activate'),
models.py
...
token = models.CharField(max_length=200, blank=True)
signup_confirmation = models.BooleanField(default=False)
I wonder what do I need to put in my url to trigger my function?
I would rewrite your active view as a class. Here is an example:
class ActivateView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
token = kwargs['pk']
try:
obj = User.objects.get(email_token = token)
obj.signup_confirmation = True
obj.save()
return HttpResponse('Your account is verified')
except Exception as e:
return HttpResponse('Invalid token')
urls.py
path('verify/<uuid:pk>/', ActivateView.as_view(), name='activate'),
Im having a lot of problems in my first django, rest-framework application.
Im not getting the token after Im signing up, instead of
"user" : {
"_id":"1",
"username":"you",
"email":"a#a.com"
},
"token" : 'fjdjfkljgkghgjkl'
}
Im getting
{
"_id":"1",
"username":"you",
"email":"a#a.com"
}
what could be the problem here?
When Im trying to sign in, postman tells me:
{"username" :["user with this username already exist.]}
and Im trying to sign in not sign up.
why is this happening?
When Im trying to get all the todos that belongs to the user I get this error:
{"details":"Authentication credantials were not provided" }
, instead of an empty list, why is that?
how can I get the user if I have only the token?
serializer code:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('_id', 'username', 'email', 'password')
extra_kwarg = { 'password' : {
'write_only' : True
}}
class SignUpSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('_id', 'username', 'email', 'password')
extra_kwarg = { 'password' : {
'write_only' : True
}}
def create_user(self, validated_data):
user = User.objects.create_user(validated_data['username'], validated_data['email'], validated_data['password'])
return user
class SignInSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'password')
def validate(self, data):
username = data.get("username", None)
password = data.get("password", None)
user = authenticate(username=username, password=password)
if user is None:
raise serializers.ValidationError('A user with this username and password is not found.')
return user
viewset code:
class SignUpViewSet(viewsets.ModelViewSet):
serializer_class = SignUpSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data = request.data)
serializer.is_valid(raise_exception = True)
user = serializer.save()
return Response({
'user': SignUpSerializer(user, context = self.get_serializer_context()).data,
'token': AuthToken.objects.create(user)
})
class SignInViewSet(viewsets.ModelViewSet):
serializer_class = SignInSerializer
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)
})
class GetUserViewSet(viewsets.ModelViewSet):
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = UserSerializer
def get_object(self):
return self.request.user
Issues which you have listed are mainly cause of point 2.
{"username" :["user with this username already exist.]}
If you read it more carefully, it says user already exist. So it must be trying to create a user instead of letting them login. And this behavior is excepted because the way you have used serializer in your SignInViewSet.
class SignInViewSet(viewsets.ModelViewSet):
serializer_class = SignInSerializer
def post(self, request, *args, **kwargs):
serializer = SignInSerializer()
user = serializer.validate(attrs=request.data)
return Response({
'user': UserSerializer(user, context = self.get_serializer_context()).data,
'token': AuthToken.objects.create(user)
})
I have removed get_serializer because I am not fimilar with it, but it should work fine. Main take away is that use serializer.is_valid() only when you are updating or creating stuffs in database. As you can see right now it is trying to create a new user instance and that's why the error user with this username already exists. After this you should have your token and then set Headers in your Postman, if everything goes well you can access your user with request.user in your views.
UPDATE:
views.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from knox.models import AuthToken
from .serializers import SignInSerializer, SignUpSerializer, UserSerializer
# Create your views here.
class SignUpView(APIView):
serializer_class = SignUpSerializer
def post(self, request):
serializer = SignUpSerializer(data = request.data)
if serializer.is_valid():
user = serializer.save()
_, token = AuthToken.objects.create(user)
return Response({'user': serializer.data, 'token': token})
else:
return Response(serializer.errors)
serializers.py
class SignUpSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
extra_kwarg = { 'password' : {'write_only' : True }}
def create_user(self, validated_data):
user = User.objects.create_user(username=validated_data['username'], email=validated_data['email'], password=validated_data['password'])
user.save()
return user
Since you are not using viewsets I have shifted SignUpView to views.py and I have removed your extra validation from serializers as it won't work, instead if there is any error in your serializer it will be handled in your views, check if serializer.is_valid() and else statement. And your urls.py in user app will be something like this.
from rest_framework import routers
from .views import SignUpView
from django.urls import path
# user_router = routers.DefaultRouter()
# user_router.register(r'sign_up', SignUpViewSet, basename='sign_up')
# user_router.register(r'sign_in', SignInViewSet, basename='sign_in')
# user_router.register(r'user', GetUserViewSet, basename='user')
urlpatterns = [
path('sign_up/', SignUpView.as_view()),
]
and backend/urls.py will be
from django.contrib import admin
from django.urls import include,path
from todo.urls import todo_router
from django.views.generic import TemplateView
urlpatterns = [
path('admin/', admin.site.urls),
path('api/todo/', include(todo_router.urls)),
path('api/auth/', include('user.urls')),
path('', TemplateView.as_view(template_name = 'index.html'))
]
Check output:
NOTE: self.get_serializer(data = request.data) is only available for ViewSets, that's why views.py has been edited. You can similarly update your other views too.
I am new to django rest-framework, but I am able create basic apis . But I wanted to know how can I log in a user from Android.
Should I create a api view which takes post data (username,password) and then tries the login method and return if user is successfully logged in or not?
How should I proceed ?
Thanks.
first, you should serialize your user class
class UserLoginSerializer(ModelSerializer):
token = CharField(read_only=True)
phone_number = IntegerField(required=False)
class Meta:
model = User
fields = [
# 'id',
'phone_number',
'password',
'token'
]
extra_kwargs = {
'password': {
'write_only': True
}
}
def validate(self, data):
phone_number = data['phone_number']
password = data['password']
if not phone_number:
raise ValidationError('Phone number is required to login.')
user = User.objects.get(phone_number=phone_number)
if not user:
raise ValidationError('Your account does not exist.')
elif not user.check_password(password):
raise ValidationError('Incorrect password, please try again.')
return data
then, you should create your login view class
class UserLoginAPIView(CreateAPIView):
serializer_class = UserLoginSerializer
queryset = User.objects.all().filter(is_active=True)
def post(self, request, *args, **kwargs):
serializer = UserLoginSerializer(data=request.data)
user = User.objects.all()
try:
user =
user.get(Q(phone_number__iexact=request.data['phone_number']))
except:
return Response({'error': 'user does not exist.'}, status=status.HTTP_404_NOT_FOUND)
if serializer.is_valid(raise_exception=True):
# serializer.save()
return Response({'id': user.id}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_404_NOT_FOUND)
after doing this two thing you need to add your view class to a URL
url(r'^login/$', UserLoginAPIView.as_view(), name='user-login')
I am using 'rest_framework_jwt.authentication' and also i have Login API, if i am generating token over default auth-token API then what is the use of Login API?
so can i generate token while executing login API and if its success then send back generated token to the front-end.
urls.py
url(r'^login/$', views.UserLoginAPIView.as_view(), name='login'),
url(r'^api/auth/token/', obtain_jwt_token),
serializers.py
class UserLoginSerializer(ModelSerializer):
token = CharField(allow_blank=True, read_only= True)
email = EmailField(label='Email Address', allow_blank= True)
class Meta:
model = User
fields = [
'email',
'password',
'token'
]
extra_kwargs = {"password":
{"write_only": True}
}
def validate(self, data):
user_obj = None
email = data.get("email", None)
password = data["password"]
if not email:
raise ValidationError('A username or email is required to login')
user = User.objects.filter(
Q(email=email)
).distinct()
if user.exists() and user.count() == 1:
user_obj = user.first()
else:
raise ValidationError("this email is not valid")
if user_obj:
if not user_obj.check_password(password):
raise ValidationError("incorrect creadeintial try again")
data["token"] = "SOME BLANK TOKEN"
return data
view.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 only need one login API, so pick if you want to write your own or use a provided one. If all you want is to return the token, just use the provided one. But you may want to do other stuff in a custom login. For example, you could also return more information about the user (e.g. the avatar).
It makes a lot of sense to return the token on login, so you just have to return that with
from rest_framework_jwt.settings import api_settings
def get_jwt_token(user):
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
return jwt_encode_handler(payload)
And then you can get the JWT token like this
data["token"] = get_jwt_token(request.user)
But I personally don't believe it is best to do this from the serializer validator. Here is how I implement my own login function:
from django.contrib.auth import authenticate, login
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.parsers import JSONParser
class APILoginViewSet(APIView):
"""
Returns user info and tokens if successful
"""
#csrf_exempt
def post(self, request, format=None):
"""
Secondary login method. Uses email and password
Recommendation: Use `POST:/api/v1/auth/api-jwt-auth/` instead
"""
data = JSONParser().parse(request)
serializer = LoginCustomSerializer(data=data)
if serializer.is_valid():
email = serializer.data.get('email')
password = serializer.data.get('password')
if not request.user.is_anonymous:
return Response('Already Logged-in', status=status.HTTP_403_FORBIDDEN)
user = authenticate(email=email, password=password)
if user is not None:
if user.is_active:
login(request, user)
serialized = UserSerializer(user)
data = serialized.data
# Also return JWT token
data['jwt'] = get_jwt_token(user)
return Response(data)
else:
return Response('This user is not Active.', status=status.HTTP_401_UNAUTHORIZED)
else:
return Response('Username/password combination invalid.', status=status.HTTP_401_UNAUTHORIZED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The code above assumes you have a UserSerializer and LoginCustomSerializer, which could be as simple as:
class LoginCustomSerializer(serializers.Serializer):
email = serializers.EmailField(max_length=200)
password = serializers.CharField(max_length=200)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email', 'username', 'created_at', 'name')
read_only_fields = ( 'email', 'username', 'created_at' )
Calling API directly from web front-end is not a good idea. You don't need any tokens if you are already logged in using session. In most cases, there is a webserver which calls the api server. The API server authenticates the user with token. The token is stored in the web server's session table. Android or IOS apps can directly call the API and get the token.
I want to return an auth token in json after successful user registration. How can I do this ?
For registration I use the following
seriazilers.py
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = [
'id',
'username',
'password',
'email',
]
write_only_fields = ('password',)
read_only_fields = ('id',)
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
)
user.set_password(validated_data['password'])
user.save()
return user
views.py
class CreateUser(CreateAPIView):
queryset = Profile.objects.all()
serializer_class = UserSerializer
The are many ways to do this. Here is example in context of your existing code. (put this in your views.py)
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
from rest_framework import status
class CreateUser(CreateAPIView):
queryset = Profile.objects.all()
serializer_class = UserSerializer
def create(self, request, *args, **kwargs): # <- here i forgot self
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
token, created = Token.objects.get_or_create(user=serializer.instance)
return Response({'token': token.key}, status=status.HTTP_201_CREATED, headers=headers)
Here's a simple solution for the user when he/she wants to login/sign-in
first of all download django-rest-framework-jwt with pip
pip install djangorestframework-jwt
in your UserSerializer add this to make sure the username and password are correct (add as many fields as you wish)
username = serializers.CharField(read_only=True)
password = serializers.CharField(read_only=True)
Now in your view.py add this
# authenticate: will check if the user exist
from django.contrib.auth import authenticate
# api_settings: will help generating the token
from rest_framework_jwt.settings import api_settings
def login_page(request):
payload_handler = api_settings.JWT_PAYLOAD_HANDLER
encode_handler = api_settings.JWT_ENCODE_HANDLER
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = authenticate(username=request.data['username'], password=request.data['password'])
if user:
payload = payload_handler(user)
token = encode_handler(payload)
return Response({'token': token})
And mainly that's it! hope it helps!