I'm a bit confused about token authentication. After a few question and attempts I've manage to create url gateway for auto-login my users but I'm managing to do that only by using user_id and passing it out in the url for example http://example.com/auth/login/?user_id=12 but I would rather do it with ?token=.
I'm using DRF example on how make custom auth token and return some more data about my user with that so after I curl to the url I'm returning
{"token":"d5d86e55fd5ddd48298b2ac72c3ed96b7e30dd86","user_id":52}
Now the problem that I'm facing is this MyUser maching query does not exists which is normal I didn't have token in my model so I've created one
token = models.CharField(max_length=125, null=True, blank=True)
so I could overcome DoesNotExist error but the error is still there.
I'm using this hack for the gateway login
from django.contrib.auth import authenticate, login
from django.core.urlresolvers import reverse
from django.views.generic import View
from django.http import HttpResponseRedirect
from business_accounts.models.my_user import MyUser
class UrlGatewayLogin(View):
def get(self, request, **kwargs):
page_group = kwargs.get('page_group')
token = request.GET.get('token')
user = MyUser.objects.get(token=token)
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
return HttpResponseRedirect(reverse('dashboard', args=(page_group, )))
Is DRF token unique per user, and how can I generate token in django and use it for my gateway?
You need to install Django-Rest-Framework and enable TokenAuthentication in the settings.
First you have to write the a serializer for your User Model, and then create an api view with the serialized data for token authentication.
For example (You can also use username instead of email):
In your users/api/serializers.py file:
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.compat import authenticate
from ..models import User
class AuthTokenSerializer(serializers.Serializer):
email = serializers.EmailField(label=_("Email Address"))
password = serializers.CharField(
label=_("Password"),
style={'input_type': 'password'},
trim_whitespace=False
)
def validate(self, data):
email = data.get('email')
password = data.get('password')
if email and password:
user = authenticate(email=email, password=password)
if user:
if not user.is_active:
msg = _('User account is deactivated.')
raise serializers.ValidationError(msg)
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "email" and "password".')
raise serializers.ValidationError(msg)
data['user'] = user
return data
Then in your users/api/views.py file:
from rest_framework.response import Response
from .serializers import AuthTokenSerializer
from ..models import User
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
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, 'username':user.username})
Then in settings, make sure you have token authentication turned on as such:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
In order to return data about the user with that token, you need to write another serializer on the User model including the fields you want to return, for instance:
class UserDetailSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'username', 'email', 'hobbies', 'language',
'gender', 'birthday'
]
And write another api view such as:
class UserDetailAPIView(RetrieveAPIView):
permission_classes = [IsAuthenticated]
serializer_class = UserDetailSerializer
def get_object(self):
obj = get_object_or_404(User, username=self.request.user.username)
return obj
The key is to write serializers for your model so that the data can be transported on the Internet.
Related
This project was working before, but then Heroku took away free tier so I have been trying to deploy somewhere else, but now all of a sudden I cannot even create a user locally even though I could before... Now when I create a user I get the error mentioned in the title.
serializers folder
common.py file
from xml.dom import ValidationErr
from rest_framework import serializers
from django.contrib.auth import get_user_model, password_validation
from django.contrib.auth.hashers import make_password
User = get_user_model()
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
confirm_password = serializers.CharField(write_only=True)
def validate(self, data):
password = data.pop('password')
confirm_password = data.pop('confirm_password')
if password != confirm_password:
raise ValidationErr({ 'confirm_password': 'Does not match the password'})
password_validation.validate_password(password)
data['password'] = make_password(password)
return data
class Meta:
model = User
fields = ('id', 'username', 'email', 'password', 'confirm_password')
views.py file
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.exceptions import PermissionDenied
from django.contrib.auth import get_user_model
import jwt
User = get_user_model()
from datetime import datetime, timedelta
from jwt_auth.serializers.common import UserSerializer
from django.conf import settings
class RegisterView(APIView):
def post (self, request):
create_user = UserSerializer(data=request.data)
try:
create_user.is_valid(True)
create_user.save()
return Response(create_user.data, status=status.HTTP_201_CREATED)
except Exception as e:
print(str(e))
return Response(e.__dict__ if e.__dict__ else str(e), status=status.HTTP_422_UNPROCESSABLE_ENTITY)
class LoginView(APIView):
def post(self, request):
password = request.data.get('password')
username = request.data.get('username')
try:
user_login = User.objects.get(username=username)
except User.DoesNotExist:
raise PermissionDenied('Credentials are incorrect!')
if not user_login.check_password(password):
raise PermissionDenied('Credentials are incorrect!')
dt = datetime.now() + timedelta(days=7)
token = jwt.encode(
{
'sub': user_login.id,
'exp': int(dt.strftime('%s'))
},
settings.SECRET_KEY,
'HS256'
)
print('TOKEN ----->', token)
return Response({ 'token': token, 'message': f'Welcome back {user_login.username}' })
rest-framework branch "3.9.x" (github)
def is_valid(self, raise_exception=False):
...
rest-framework branch "master" (github)
def is_valid(self, *, raise_exception=False):
...
so "master" now: is_valid() only accepts keyword argument "raise_exception=True":
class RegisterView(APIView):
def post (self, request):
create_user = UserSerializer(data=request.data)
try:
create_user.is_valid(raise_exception=True)
....
Change it into something like this:
def post(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
return Response(user.data, status=status.HTTP_201_CREATED)
the rest of your code...
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 having problems with my login API view. When trying to login I get a POST 404 but I know the url path is correct because if I put a simple GET request in the view it returns data. There are still some rough parts of my code but I thought this would work.
login.py
from django.contrib.auth import authenticate
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from apiV1.v1.accounts.models.user import User
from apiV1.v1.accounts.serializers.user import UserSerializerLogin
# login
class LoginView(APIView):
authentication_classes = ()
permission_classes = ()
#staticmethod
def post(request):
user = get_object_or_404(User, email=request.data.get('email'))
user = authenticate(username=user.email, password=request.data.get('password'))
if user:
serializer = UserSerializerLogin(user)
return Response(serializer.data)
return Response(status=status.HTTP_400_BAD_REQUEST)
serializers.py
class UserSerializerLogin(UserSerializer):
token = serializers.SerializerMethodField()
#staticmethod
def get_token(user):
"""
Get or create token
"""
token, created = Token.objects.get_or_create(user=user)
return token.key
class Meta:
model = User
fields = ('id', 'email', 'first_name', 'last_name', 'profile', 'role', 'token')
POST
{
"email": "admin",
"password": "password"
}
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!