1
votes

I'm developing an API backend using the Django Rest Framework. I had initially developed it using Session Authentication, unaware that it could not be used for sending to a mobile application. I encountered trouble with respect to CSRF protection while trying user login in Postman.

Now, since I have to shift to Token-based Authentication to make it work, how do I go about doing so? I would like to how to implement it quickly. I have browsed through tutorials and answers on stackoverflow, but am unable to implement this in practise

Also, is Token Authentication the most suitable method for authentication? Should I use the default provided DRF module or JWT or some other implementation? Could I use token authentication simply for user login, and session authentication for the other 3 APIs?

class UserLogin(APIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def post(self, request, format='json'):
    username = request.POST.get('username')
    email = request.POST.get('email')
    password = request.POST.get('password')
    user = EmailBackend.authenticate(self,username = email, password = password)
    if user:
        id = user.id
        return Response(id, status=status.HTTP_201_CREATED)
    else:
        return Response("Failure", status=HTTP_400_BAD_REQUEST)

class UserRegistration(APIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    def post(self, request, format='json'):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
             user = serializer.save()
             if user:
             return Response('Success', status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

class RecommendationQuestions(generics.ListCreateAPIView):
    def post(self, request, format = 'json'):
        """Save the post data when logging in."""
        uid = request.data['user_id']
        resp_list = MovieSerializer.provide_movie_choices(uid)
        return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)

class RecommendationGenerator(generics.ListCreateAPIView):
    queryset = Ratings.objects.all()#.filter(id__in=(1,2))
    serializer_class= RatingsSerializer#(queryset,many=True)
    def post(self, request, format='json'):
        many = isinstance(request.data, list)
        serializer = RatingsSerializer(data = request.data, many = many)
        x = 0
        if serializer.is_valid():
            uid = [d['userId'] for d in serializer.data]
            resp_list = RatingsSerializer.generate_recommendations(self, uid[0])
            return Response(resp_list, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)

This is the views.py for the APIs.

2

2 Answers

10
votes

Token authentication setup

You enable TokenAuthentication by including

'rest_framework.authtoken'

in INSTALLED_APPS settings (documentation).

You must run migrate after that. After you run migration, you need to create tokens for your users. Here is an example code that does that:

from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token

users = User.objects.all()
for user in users:
    token, created = Token.objects.get_or_create(user=user)

You run this only once. Also, you need to create token for every new user. You can automate that with post_save signal:

from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from rest_framework.authtoken.models import Token

@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance) 

Additionally, you have to add configure authentication classes by including

'rest_framework.authentication.TokenAuthentication'

in your settings 'DEFAULT_AUTHENTICATION_CLASSES' (documentation)

Last thing you need to do is add url for token authentication to your urls.py:

from rest_framework.authtoken import views as drf_views

urlpatterns += [
    path('api-token-auth/', drf_views.obtain_auth_token)
]

Session-based authentication is meant for logging to your API with your browser. Token-based Authentication is stateless, which means that the server doesn't store any state about the client session on the server. Read more about the difference here. If you login via Token-based authentication, you won't have a session and won't be able to access API in any other way but via token.

Authentication example

Below is a sample code for token authentication in Python with the use of requests library.

# Authentication
import requests
r = requests.post(<add your token auth url here>, data={'username': 'my_username', 'password': 'my_password'})
if r.status_code == 200:
    response = r.json()
    token = response['token']
    print(token)

Token must be used for every other API request. It's sent via headers.

# Consume API
import requests
headers = {'Authorization': 'Token {}'.format(<your token here>)}
# Request method is either GET, POST, PUT, PATCH or DELETE
r = requests.request(method=<request method>, url=<url to api>, headers=headers)

# or you can also use
# requests.get(url=<url to api>, headers=headers) or
# requests.post(url=<url to api>, headers=headers, data=<your data>) etc.
2
votes

I would recommend you to use JWT, it much more safety than what rest_framework.authtoken is provided Such as a pair of token/refresh token to set for your main token small expiration time. That reduces the chance for the token to be stolen or corrupted. Also inside your JWT token, you can store payload which is very useful in many cases.

There is a very good library for DRF which implements all aspects of using JWT with DRF and it's pretty flexible to adapt to your purposes.

http://getblimp.github.io/django-rest-framework-jwt/

Could I use token authentication simply for user login, and session authentication for the other 3 APIs?

Yes, you definitely can. Each instance of APIView has property 'authentication_classes' and you can set SessionAuthentication specifically for APIs you want.

For example:

class RecommendationQuestions(generics.ListCreateAPIView):
        authentication_classes = (SessionAuthentication, )

        def post(self, request, format = 'json'):
            """Save the post data when logging in."""
            uid = request.data['user_id']
            resp_list = MovieSerializer.provide_movie_choices(uid)
            return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)

Or you can use both

class RecommendationQuestions(generics.ListCreateAPIView):
        authentication_classes = (SessionAuthentication, JSONWebTokenAuthentication)

        def post(self, request, format = 'json'):
            """Save the post data when logging in."""
            uid = request.data['user_id']
            resp_list = MovieSerializer.provide_movie_choices(uid)
            return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)