Add rest_framework.authtoken to INSTALLED_APPS in settings.py
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)
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)