API to generate authentication tokens

Example of an API that validates POST data and returns some value

Add rest_framework.authtoken to INSTALLED_APPS in settings.py

What should this API do?

The API client may not want to send users ID and password for every request. In such cases, the user may generate a token which will be used for authentication

1) The API should be able to create token only for existing user

2) The API should not create token when provided with invalid credentials

from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status

TOKEN_URL = reverse("user:token")


def create_user(**kwargs):
    return get_user_model().objects.create_user(**kwargs)


class UserTokenTests(TestCase):
    """The API client may not want to send users ID and password
        for every request. In such cases, the user may generate a token
        which will be used for authentication"""

    def setUp(self):
        self.client = APIClient()

    # test if token can be created successfully
    # with valid creds for existing user
    def test_create_token_for_user(self):
        payload = {'email': 'test@test.com', 'password': 'testpass'}
        create_user(**payload)
        res = self.client.post(TOKEN_URL, payload)

        self.assertIn('token', res.data)
        self.assertEqual(res.status_code, status.HTTP_200_OK)

    # test if token cannot be created with invalid credentials
    def create_token_invalid_credentials(self):
        create_user(email='test@test.com', password='testpass')
        payload = {'email': 'test@test.com', 'password': 'wrong'}

        res = self.client.post(TOKEN_URL, payload)

        self.assertNotIn('token', res.data)
        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

    # test if token cannot be created for non existing user
    def create_token_no_user(self):
        payload = {'email': 'test@test.com', 'password': 'testpass'}
        res = self.client.post(TOKEN_URL, payload)
        self.assertNotIn('token', res.data)
        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

    # test if token is not created when a fied is left blank
    def create_token_missing_field(self):
        payload = {'email': 'test@test.com', 'password': 'testpass'}
        create_user(**payload)
        payload = {'email': 'test@test.com', 'password': ''}
        res = self.client.post(TOKEN_URL, payload)
        self.assertNotIn('token', res.data)
        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

Implementing the API

1) In serializers.py

from django.contrib.auth import authenticate
from django.utils.translation import ugettext_lazy as _
# Serializer can also be used without a model
# Since this is not a model, we inherit from serializers.Serializer
# This is for an API that will get some data from user, validate it
# and return some value
class AuthTokenSerializer(serializers.Serializer):
    """serializer for user authentication object"""
    # create fields to get data for authentication
    email = serializers.CharField()
    password = serializers.CharField(
                style={'input_type': 'password'},
                trim_whitespace=False
                )

    # override validate method and raise exception if invalid
    def validate(self, attrs):
        # attrs contains all the serializer fields defined above
        email = attrs.get('email')
        password = attrs.get('password')

        user = authenticate(
                request=self.context.get('request'),
                username=email,
                password=password
            )
        if not user:
            # we use gettext to enable language tranlation for this text
            msg = _("Unable to authenticate with credentials provided")
            # pass correct code will raise the relavant http status code
            raise serializers.ValidationError(msg, code='authentication')

        attrs['user'] = user
        return attrs

2) In views.py

from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.settings import api_settings

# view for API validating user credentials and providing token
class CreateTokenView(ObtainAuthToken):
    serializer_class = AuthTokenSerializer
    # class that will render this page
    # works without this but does not create nice view in the browser
    # as it did when extended from generic views
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES

3) On typing the url on the browser, (but this link should be called from client code with POST dict)

4) This token will be stored and used for authentication (will be demonstrated in future sections)