24

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 :).

2
  • Does your app already have a way to login using email and password? Or is this the first authentication method that you're implementing for the app? Commented Jan 21, 2015 at 2:02
  • Hi... is the first authentication method, i have not implemented other before... now i'm using the default get token method, using username and password... but, in mobile devices, i need to get the token auth using email and password. Commented Jan 21, 2015 at 2:07

5 Answers 5

41

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Hello! I tried your solution, but it complains that methods validateEmail and authenticate are missing. Can you please share the missing code? Thanks!
9

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":"[email protected]",
    "password":"Pa$$w0rd"
}

3 Comments

Is it DRF settings or global part of settings.
I found that sending json and use field "username" but value is email, still work. No need to add ACCOUNT_ in the settings.py.
It seems like this is from django-allauth, not drf
7

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!!

Comments

0

@udeep shrestha solution nearly works but you will need to change the validate method to manually use Django's user auth model to manually authenticate with an email:

from rest_framework import serializers
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User


class UserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    username = serializers.CharField()
    first_name = serializers.CharField()
    last_name = serializers.CharField()
    is_staff = serializers.BooleanField()
    date_joined = serializers.DateTimeField()


class CustomAuthTokenSerializer(serializers.Serializer):
    email = serializers.EmailField(
        label=_("Email"),
        write_only=True
    )
    password = serializers.CharField(
        label=_("Password"),
        style={"input_type": "password"},
        trim_whitespace=False,
        write_only=True
    )
    token = serializers.CharField(
        label=_("Token"),
        read_only=True
    )

    def validate(self, attrs):
        email = attrs.get("email")
        password = attrs.get("password")
        passwords_match = False
        if email and password:
            user = User.objects.get(email=email)
            if user:
                passwords_match = user.check_password(password)

            # The authenticate call simply returns None for is_active=False
            # users. (Assuming the default ModelBackend authentication
            # backend.)
            if not user or not passwords_match:
                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

Also I have updated the extended ObtainAuthToken class for Django >= 5:

from rest_framework.authtoken import views as auth_views
from rest_framework import parsers, renderers
from rest_framework.compat import coreapi, coreschema
from rest_framework.schemas import ManualSchema
from rest_framework.schemas import coreapi as coreapi_schema
from user.serializers import CustomAuthTokenSerializer


class CustomAuthToken(auth_views.ObtainAuthToken):
    throttle_classes = ()
    permission_classes = ()
    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
    renderer_classes = (renderers.JSONRenderer,)
    serializer_class = CustomAuthTokenSerializer

    if coreapi_schema.is_enabled():
        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",
        )

Comments

-1

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

3 Comments

Not valid for django <2.0 versions
This is a way to get the token, but it is not what the OP's question is asking.
I think, it's way better than that. P.S. You get to add CSRF tokens.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.