I'm working on a project to enable the django rest framework authentication for mobile devices. I'm using the default token authentication for get the user token from a post request sending username and password.
curl --data "username=username&password=password" http://127.0.0.1:8000/api/api-token-auth/
(api/api-token-auth/ is the url configured with the obtain_auth_token view)
urlpatterns = [
url(r'^api/api-token-auth/', obtain_auth_token),
url(r'^', include(router.urls)),
]
and the response is the user token.
{"token":"c8a8777aca969ea3a164967ec3bb341a3495d234"}
I need to obtain the user token auth using email-password on the post instead username-password, or both. I was reading the documentation of custom authentication http://www.django-rest-framework.org/api-guide/authentication/#custom-authentication... but really, isn't very clear to me.
It's very helpful to me... thanks :).
Ok,I found a way for get the auth token using email or username... This is the serializer:
class AuthCustomTokenSerializer(serializers.Serializer):
email_or_username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
email_or_username = attrs.get('email_or_username')
password = attrs.get('password')
if email_or_username and password:
# Check if user sent email
if validateEmail(email_or_username):
user_request = get_object_or_404(
User,
email=email_or_username,
)
email_or_username = user_request.username
user = authenticate(username=email_or_username, password=password)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise exceptions.ValidationError(msg)
else:
msg = _('Unable to log in with provided credentials.')
raise exceptions.ValidationError(msg)
else:
msg = _('Must include "email or username" and "password"')
raise exceptions.ValidationError(msg)
attrs['user'] = user
return attrs
In the email_or_username field, the user can send the email or the username, and using the function validateEmail(), we can check if the user is trying to login using email or username. Then, we can make the query for get the user instance if is valid, and authenticate it.
This is the view.
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (
parsers.FormParser,
parsers.MultiPartParser,
parsers.JSONParser,
)
renderer_classes = (renderers.JSONRenderer,)
def post(self, request):
serializer = AuthCustomTokenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
content = {
'token': unicode(token.key),
}
return Response(content)
and then:
curl --data "email_or_username=emailorusername&password=password" http://127.0.0.1:8000/api/my-api-token-auth/.
It's ready.
Write these requirements into your settings.py
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
To check, send this json format request to your server:
{
"username":"youremail#mail.domain",
"password":"Pa$$w0rd"
}
Change the default serializer the library is using for example in auth/serializers.py
from django.contrib.auth import authenticate
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
class MyAuthTokenSerializer(serializers.Serializer):
email = serializers.EmailField(label=_("Email"))
password = serializers.CharField(
label=_("Password",),
style={'input_type': 'password'},
trim_whitespace=False
)
def validate(self, attrs):
email = attrs.get('email')
password = attrs.get('password')
if email and password:
user = authenticate(request=self.context.get('request'),
email=email, password=password)
# The authenticate call simply returns None for is_active=False
# users. (Assuming the default ModelBackend authentication
# backend.)
if not user:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg, code='authorization')
else:
msg = _('Must include "email" and "password".')
raise serializers.ValidationError(msg, code='authorization')
attrs['user'] = user
return attrs
Override the view for example in auth/views.py
from rest_framework.authtoken import views as auth_views
from rest_framework.compat import coreapi, coreschema
from rest_framework.schemas import ManualSchema
from .serializers import MyAuthTokenSerializer
class MyAuthToken(auth_views.ObtainAuthToken):
serializer_class = MyAuthTokenSerializer
if coreapi is not None and coreschema is not None:
schema = ManualSchema(
fields=[
coreapi.Field(
name="email",
required=True,
location='form',
schema=coreschema.String(
title="Email",
description="Valid email for authentication",
),
),
coreapi.Field(
name="password",
required=True,
location='form',
schema=coreschema.String(
title="Password",
description="Valid password for authentication",
),
),
],
encoding="application/json",
)
obtain_auth_token = MyAuthToken.as_view()
Hook up the url for example in auth/urls.py
from .views import obtain_auth_token
urlpatterns = [
re_path(r'^api-token-auth/', obtain_auth_token),
]
and you are ready to go!!
There is a cleaner way to get the user token.
simply run manage.py shell
and then
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User
u = User.objects.get(username='admin')
token = Token.objects.create(user=u)
print token.key
Related
I have a user logi form i am using to authenticate users in django and this is the code that processes
from django.contrib.auth.hashers import make_password
from django.contrib.auth.hashers import check_password
from django.contrib.auth import login
def process_login(request):
if request.method == 'POST':
var = request.POST["email_or_telephone"]
print(var)
obj=AuthUsers.objects.create(
email_address = "info#google.com",
user_type = "inapp",
telephone_number = "08002556155",
user_password = make_password("logan2900"),
account_status = "not_verified",
full_names = "Michael Jackson",
pronouns = "Hee/Hee",
country = "UK",
gps_location = "London",
role_name = "user",
profile_picture = "image.png",
kyc_documents = "1.png,2.jpg"
)
obj.save()
if check_password("logan2900", "pbkdf2_sha256$320000$S8u10Fh1yz0NssYphC1qW1$LvnhBHACIqr6dGX7Bae19k8/yGf/omNLQcvl88QXodv="):
print("Correct Password")
else:
print("Incorrect Password")
I am using the the check_password method to check if a user password is correct and the function works as expected.
How can i start session and actually login user after verifying the password is correct?
Also after successfully logging the user, how can i verify a user is logged in in another function?
I'm working on a project where user can register using his mobile no and password( after verifying with otp) what i'm doing is inside username field i'm saving user's phone no. as username is a mandatory field. And I'm using simple_jwt to get access token and refresh token. Everything is working fine
urls.py
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns =[
path('register', RegisterView.as_view() ),
path('otp/send', SendOtpView.as_view() ),
path('otp/verify', VerifyOtpView.as_view() ),
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
instead of creating a loginSerializer i'm using rest_framework_simplejwt inbuild class TokenObtainPairView
But when i go to the url
auth/api/token/ it ask me for username and password, which is confusing as a user . how can i change the name of the username to phone.
I don't have any idea how to do it as I'm new to the djangorestframework
serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class LoginSerializer(TokenObtainPairSerializer):
password = serializers.CharField(max_length=65, min_length=8, write_only=True)
**phone = serializers.CharField(max_length=20, source='username')**
class Meta:
model = User
fields = ['phone', 'password']
I tried doing this but then it add on another field with the name phone instead of replacing username. I even don't know whether it will work or not .
Ok. Somehow i found a way to do it. I created a model serializer an used an custom token generator method
https://django-rest-framework-simplejwt.readthedocs.io/en/latest/creating_tokens_manually.html
serializers.py
# for creating token manually
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
class LoginSerializer(serializers.ModelSerializer):
password = serializers.CharField(max_length=65, min_length=8, write_only=True)
phone = serializers.CharField(max_length=20, source='username')
tokens = serializers.SerializerMethodField(read_only=True)
def get_tokens(self, obj):
return obj['tokens']
class Meta:
model = User
fields = ['phone', 'password', 'tokens']
def validate(self, attrs):
param_phone = attrs.get('username', "")
param_password=attrs.get('password', '')
if not param_phone.isnumeric():
raise serializers.ValidationError(
{'phone': ("Only Phone Number is accepted")}
)
if not len(param_phone) == 10:
raise serializers.ValidationError(
{'phone': ("Phone Number length can only be of 10 digits")}
)
user = auth.authenticate(username=param_phone, password=param_password)
if User.objects.filter(username=param_phone, is_active=False).exists():
raise AuthenticationFailed("Account disabled. Contact admin")
if not user:
raise AuthenticationFailed("Invalid Credential. Try again")
tokens = get_tokens_for_user(user)
return {
'username':param_phone,
'tokens': tokens
}
return super().validate(attrs)
views.py
class LoginView(GenericAPIView):
serializer_class = LoginSerializer
def post(self, request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)
How could i achieve email functionality using drf as backeend and django to hit these apis.What i need how will user be confirm from django while using drf to send activation link.
At first, you need to add the code to send the verification email when you register.
from base64 import urlsafe_b64decode, urlsafe_b64encode
from django.contrib.auth.tokens import default_token_generator
from django.template.loader import render_to_string
from threading import Thread
class EmailRegisterView(APIView):
"""APIs for Email Registeration"""
permission_classes = [AllowAny]
def post(self, request):
"""Signup with Email"""
serializer = EmailRegisterSerializer(data=request.data)
if serializer.is_valid():
...
user.save()
// send verification link
cur_token = default_token_generator.make_token(user)
email = urlsafe_b64encode(str(user.email).encode('utf-8'))
# now send email
mail_subject = 'Email Confirmation'
message = render_to_string('emails/email_verification.html', {
'site_url': settings.SITE_URL,
'token': f'api/users/verify/{email.decode("utf-8")}/{cur_token}',
})
t = Thread(target=send_mail, args=(
mail_subject, message, settings.EMAIL_FROM_USER, to_email))
t.start()
return Response({
"success": True,
"user": MemberSerializer(user).data
}, status.HTTP_200_OK)
And you can add the confirmation view.
urlpatterns = [
...
path('verify/<str:email>/<str:email_token>',
verify_email, name="verify_token"),
...
]
Then the verify_email function verifies the token and redirects.
#api_view(['GET'])
#permission_classes([AllowAny])
def verify_email(request, email, email_token):
"""Verify Email"""
try:
target_link = settings.CLIENT_URL + "/account/result?type=email_verified"
if verify_token(email, email_token):
return redirect(target_link)
else:
return render(
request,
"emails/email_error.html",
{'success': False, 'link': target_link}
)
except BaseException:
pass
Here is the verify_token function.
def verify_token(email, email_token):
"""Return token verification result"""
try:
users = Member.objects.filter(
email=urlsafe_b64decode(email).decode("utf-8"))
for user in users:
valid = default_token_generator.check_token(user, email_token)
if valid:
user.is_verified = True
user.save()
return valid
except BaseException:
pass
return False
I'm using all-auth and dj-rest-auth to implement registration and login via email. Everything work fine but when making test, inactive users return invalid credentials message instead inactive account message. In the LoginSerializer, it seems that django.contrib.auth authenticate method doesn't return the user, but None. Here is the code:
settings.py
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.AllowAllUsersModelBackend",
"allauth.account.auth_backends.AuthenticationBackend"
]
REST_AUTH_REGISTER_SERIALIZERS = {
'REGISTER_SERIALIZER': 'user.serializers.RegisterSerializer',
}
REST_AUTH_SERIALIZERS = {
'LOGIN_SERIALIZER': 'user.serializers.LoginSerializer',
'USER_DETAILS_SERIALIZER': 'user.serializers.UserDetailSerializer',
}
serializers.py
class LoginSerializer(serializers.Serializer):
email = serializers.EmailField(required=True, allow_blank=False)
password = serializers.CharField(style={'input_type': 'password'})
def authenticate(self, **kwargs):
return authenticate(self.context['request'], **kwargs)
def _validate_email(self, email, password):
user = None
if email and password:
user = self.authenticate(email=email, password=password)
else:
msg = _('Must include "email" and "password".')
raise exceptions.ValidationError(msg)
return user
def validate(self, attrs):
email = attrs.get('email')
password = attrs.get('password')
user = None
if 'allauth' in settings.INSTALLED_APPS:
from allauth.account import app_settings
# Authentication through email
if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.EMAIL:
user = self._validate_email(email, password)
# Did we get back an inactive user?
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise exceptions.ValidationError(msg)
else:
msg = _('Unable to log in with provided credentials.')
raise exceptions.ValidationError(msg)
# If required, is the email verified?
if 'dj_rest_auth.registration' in settings.INSTALLED_APPS:
from allauth.account import app_settings
if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
try:
email_address = user.emailaddress_set.get(email=user.email)
except:
raise serializers.ValidationError(_('E-mail is not registered.'))
else:
if not email_address.verified:
raise serializers.ValidationError(_('E-mail is not verified.'))
attrs['user'] = user
return attrs
tests.py
########################################################################
# LOG IN WITH INACTIVE USER
login_data = {
'email': 'inactive#inactive.com',
'password': '9I8u7Y6t5R4e'
}
response = self.client.post('http://localhost:8000/api/auth/login/', login_data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
expected_error = {
'non_field_errors': 'User account is disabled.'
}
response_error = {
'non_field_errors': response.data['non_field_errors'][0]
}
self.assertEqual(response_error, expected_error)
Is there something that I missing?
Thanks in advance.
In case anyone is interested, I found the problem: the allauth authentication backend override the django model backend. In order to resolve this, I create a class that inherit from allauth backend and add the function that allow all users to log in:
backend.py
from allauth.account.auth_backends import AuthenticationBackend
class AllowAllUsersModelBackend(AuthenticationBackend):
def user_can_authenticate(self, user):
return True
Then add it to settings:
settings.py
AUTHENTICATION_BACKENDS = [
"user.backends.AllowAllUsersModelBackend",
]
I use VueJS and Django + django-graphql-jwt (which returns tokens). I want to use email\password fields for registration and for login. On a server I want to check if provided email already exists -> use django-graphql-jwt's mutation token_auth = graphql_jwt.ObtainJSONWebToken.Field() to return token + message, else -> create a new user with provided email and password and return this user + message.
For now I have:
# schema.py
import graphene
import graphql_jwt
from django.contrib.auth import get_user_model
from graphene_django import DjangoObjectType
class UserType(DjangoObjectType):
class Meta:
model = get_user_model()
class CreateUser(graphene.Mutation):
user = graphene.Field(UserType)
class Arguments:
password = graphene.String(required=True)
email = graphene.String(required=True)
def mutate(self, info, password, email):
user = get_user_model()(
username=email.split('#')[0],
email=email,
)
user.set_password(password)
user.save()
return CreateUser(user=user)
# Want to combine two mutations here
class GetOrCreateUser(graphene.Mutation):
user = graphene.Field(UserType)
class Arguments:
password = graphene.String(required=True)
email = graphene.String(required=True)
def mutate(self, info, password, email):
# Return token
if get_user_model().objects.filter(email=email).exists():
# Just an idea, what I want to get here (to be returned to VueJS)
return {'token': graphql_jwt.ObtainJSONWebToken.Field(), 'msg': 'Token Generated'}
# Create new user
else:
user = CreateUser(???)
return {'user': user, 'msg': 'User is Created'}
class Mutation(graphene.ObjectType):
create_user = CreateUser.Field()
get_or_create_user = GetOrCreateUser.Field()
I tried different variants, but it seems I do not understand fully graph's workflow or\and what magic Graphene does (its documentation not informative).
Can anyone show me, how can I re-use in if\else block already created code (CreateUser class and graphql_jwt.ObtainJSONWebToken.Field())?
django-graphql-auth does exactly what you’re looking for.
It extends django-graphql-jwt to handle all user account related actions.
here is the registration mutation from the docs:
mutation {
register(
email:"skywalker#email.com",
username:"skywalker",
password1: "supersecretpassword",
password2:"supersecretpassword"
) {
success,
errors,
token,
refreshToken
}
}
It already return the token and the refresh token from the django-graphql-jwt.
Than you can use to login, from the docs:
mutation {
tokenAuth(
# username or email
email: "skywalker#email.com"
password: "supersecretpassword"
) {
success,
errors,
token,
refreshToken,
unarchiving,
user {
id,
username
}
}
}
PS: I'm the author
You can create one function and use it in both mutations:
def get_or_create_user(password, email):
if get_user_model().objects.filter(email=email).exists():
return ('user or token or other data that you need in'
'mutation, this is not mutation return value')
# {'token': graphql_jwt.ObtainJSONWebToken.Field(),
# 'msg': 'Token Generated'}
# Create new user
else:
user = get_user_model()(
username=email.split('#')[0],
email=email,
)
user.set_password(password)
user.save()
return ('user or token or other data that you need in'
'mutation, this is not mutation return value')
# {'user': user,
# 'msg': 'User is Created'}
I did not find how I can call mutation from mutation (how I can call class CreateUser from class GetOrCreateUser, so I decided to integrate CreateUser to GetOrCreateUser).
I did not find, how I can call graphql_jwt via a code like (example is taken from graphene doc)
schema = graphene.Schema(...)
result = schema.execute('{ name }')
so I decided to take token directly via Python instead of Python\GraphQL.
Below is the full code:
# schema.py (the root one)
import graphene
import graphql_jwt
import apps.users.schema
class Query(apps.users.schema.Query, graphene.ObjectType):
pass
class Mutation(apps.users.schema.Mutation, graphene.ObjectType):
verify_token = graphql_jwt.Verify.Field()
refresh_token = graphql_jwt.Refresh.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
and
# users/schema.py
import graphene
from django.contrib.auth import get_user_model
from graphene_django import DjangoObjectType
from graphql_jwt.shortcuts import get_token
class UserType(DjangoObjectType):
class Meta:
model = get_user_model()
class GetOrCreateUser(graphene.Mutation):
"""
mutation {
getOrCreateUser(email: "test#domain.com", password: "YourPass") {
token
user {
id
email
isActive
}
}
}
"""
user = graphene.Field(UserType)
token = graphene.String()
class Arguments:
password = graphene.String(required=True)
email = graphene.String(required=True)
def mutate(self, info, password, email):
token = ''
# Return token
if get_user_model().objects.filter(email=email).exists():
user = get_user_model().objects.get(email=email)
token = get_token(user)
# Create new user
else:
user = get_user_model()(
username=email.split('#')[0],
email=email,
)
user.set_password(password)
user.save()
return GetOrCreateUser(user=user, token=token)
class Query(graphene.ObjectType):
users = graphene.List(UserType)
def resolve_users(self, info):
return get_user_model().objects.all()
FrontEnd can define the state by checking token field. If empty, than user is created, if there is something, than user exists and just logged in.