Django REST Framework TokenAuthentication
Tags: python, beginners, django, programming
Published: 2021-09-16 16:06:12

This article assumes you're familiar with [Django](https://www.djangoproject.com/) and the basics of the [Django REST framework](https://www.django-rest-framework.org) web framework.

Implementing a token authentication in REST framework is not quite straightforward. After browsing the docs and scanning through blog posts, I found an "easy" way to implement the *TokenAuthentication* scheme.

Before delving in, it's best to explain just a bit what Django REST framework is, and some of its essential concepts.

## Django REST Framework (DRF).

The _Django REST framework_ is python framework for building decoupled and ["API-first"](https://romeopeter.com/api-first) web application. It allows you to convert your existing Django models to format that translated to and from [JSON](https://wikipedia.com/json).

As the name suggests, DRF is built on top of Django framework. In fact, it was built to enable building REST API using the Django framework. 

## Key Concepts of DRF

### Serializer

"Serializer" in DRF allow the serializing and deserializing Django model instances into different representation, such as json. This is done by creating a `serializer.py` file in the desired directory followed by the corresponding code responsible for doing the serialization.

### View

Just as Django has the `views.py` file responsible for building logic and presenting data to the client in different forms. DRF builds on top that idea and provide its own views for presenting data. They are two ways to represent views in DRF: function-based views and class-based views.

In this post, I'll be using class-based views that'll inherit from REST framework's `APIView`.

Why the `APIView`?

* It's optimize for content negotiation and setting the correct renderer on the response.
* It catches all `APIException` exceptions and returns appropriate responses.
* Incoming requests are inspected and appropriate permission and/or throttle checks will be run before dispatching the request to the handler method.

> <small><b>NOTE</b></small>
>
> Some developers prefer the naming the _views_ file `api.py`. This is to keep separated from Django's `views.py` file.

### Requests and responses

The REST framework provides a `Response` object that extends Django's `HttpRequest`. The extension allows it to provide a robust way of parsing requests. The `Response` object provide a `request.data` attribute that's similar to Django view's `request.POST` but its more suitable for building web APIs.

### Routing

The REST framework  has the `router`module for API URL routing to Django. It provides a consistent way of writing view logic to a set of URLs. To create routes for URLs, you create a `router.py` file in the desired directory and then `include` it in the Django URLconf (URL configuration).

The implementation in this post will not be  using the REST framwork router, but Django's URLconf.

### Token authentication

This is an HTTP authentication scheme that uses token as means for verifying and granting access to clients. Only clients with valid token granted access. A token is passed as a payload to the HTTP `Authorization` header for every request. The server receives the token and checks it with what it has stored. If the tokens match, then the client is verified and given access.

## Installation and Configuration

To set up an isolated environment, you have use a [virtual environment]().

### Installing Django & DRF

If the virtual environment is set up and activated, run  `pip install django djang-restframework` in the terminal to install Django and DRF.

To start a project, run `python django-admin startproject [project name]` to start a new project. I'll be calling mine *authentication*.

### Installed Apps

Django has to recognize and use the REST framework and the token authentication scheme. Add them to the list of installed apps in the *settings.py* file.

```python
# Application definition

INSTALLED_APPS = [
    ...
    "rest_framework",
    "rest_framework.authtoken"
    ...
   
]
```

### Setting Authentication Class & Permission.

Still in the *settings.py* file, the default authentication scheme will be set to `TokenAuthentication` and permission to `AllowAny`. The `AllowAny` is a general setting that applies to all `views` of application, it allows the client to have access to all available views of the web app. However, this can be further streamlined to restrict certain views to authenticated users only, which you'll see as you move on.

```python
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.TokenAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.AllowAny",
    ]
}
```

### Migration.

To apply changes that require database integration (Django comes with default *SQLite*), they have to be *migrated*. Run `python manage.py migrate` to apply such changes.

## TokenAuthentication.

TokenAuthentication is differs from SessonAuthentication. No state is created on the server when using a token authentication scheme. Only the validity of the token is checked. If there's a match, the user is granted access, or more definitely, is authenticated.

### Create User.

A user account is needed to test the implementations. Django's `manage.py` command utility can be used to create a super user account.

```
python manage.py createsuperuser --username johndoe --email johndoe@gmail.com
```

### Test  View

Let's create a view with permission set only for authenticated users. Requests sent to the view with an invalid token won't be granted access. The view will handle a *GET* request that return a message of *Hello, World!* for valid tokens.

```python
# views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated 

class HelloView(APIView):
    permissions_classes = [IsAuthenticated] # <-------- Only authenticated users can access this view
    
    def get(self, request):
        context = {"message": "Hello, World!"} # <------ Response to the client
        return Response(context)
```

Add url pattern and view to the URLconf file, *urls.py*

```python
# urls.py
 from django.urlsconf import path
 from authentication.views import HelloView

urls_patterns = [
    path("/api-token-auth", HelloView.as_view())
]
```

Let's test the view. A request without token will be sent.

Python has a lightweight HTTP client package called *httpie*. Install it and follow along.

```bash
..authentication> http http://localhost:8000/api-is-authenticated
HTTP/1.1 401 Unauthorized
Allow: GET, HEAD, OPTIONS
...
Content-Type: application/json
...
WWW-Authenticate: Token # <----------- Take note of this
...
{
    "detail": "Authentication credentials were not provided."
}

```

From the above, the request wasn't authenticated because no [valid] token was associated with the request. Also, if you inspect the header, you'd notice the `www-Authentication: Token` payload. That is to tell the client that a token is needed to access data on the `http://localhost:8000/api-token-auth` URL. Later on, we'll test the view again, but with a valid token.

### Registration

The registration authentication is responsible for handling registration and creating user account. JSON request from the client will hold the credentials required to create the account. If credentials are valid, the account will be created.

To check if data is valid, it has to cross-check with serialized model data.

Create a `serializers.py` file and write the following code:

```python

# serializers.py

from django.contrib.auth.models import User

from rest_framework import serializers


class RegistrationSerializer(serializers.ModelSerializer):
     class Meta:
        model = User
        fields = [
            "id", 
            "username", 
            "first_name", 
            "last_name", 
            "email", 
            "password", 
            "is_active", 
            "is_staff"
        ]
        extra_kwargs = {"id": {"read_only": True}, "password": {"write_only": True}}
        read_only_fields

    def create(self, validated_data):
        
        username = validated_data["username"]
        first_name = validated_data["first_name"]
        last_name = validated_data["last_name"]
        email = validated_data["email"]
        password = validated_data["password"]

        user = User.objects.create_user(
            username=username,
            first_name=first_name,
            last_name=last_name,
            email=email,
            password=password,
        )

        return user
```

For the view code to handle logic for incoming request. The request will be a *POST* request.

The view will pass incoming data to the `RegistrationSerializer` class for validating and creating the user.  The serializer will return an object that tells if the data is valid or not. The view will check if data is valid. If valid, it'll save it, generate token, and return response. If not valid, it'll return error response. 

```python

# views.py

from django.contrib.auth.models import User

from authentication.serializers import RegistrationSerializer

from rest_framework import serializers
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.authtoken.models import Token
from rest_framework import permissions

class RegistrationView(APIView):
     """Registeration View"""
        
    def post(self, request, *args, **kwargs):
        """Handles post request logic"""
        registration_serializer  = RegistrationSerializer(data=request.data)
        
        # Generate tokens for existing users
        for user in User.objects.all():
            if not user:
                break
            else:
                try:
                    Token.objects.get(user_id=user.id)
                except Token.DoesNotExist:
                    Token.objects.create(user=user)
        
        if registration_class.is_valid():
            user = registration_serializer.save()
            token = Token.objects.create(user=user)
            
            return Response(
                {
                    "user": {
                        "id": serializer.data["id"],
                        "first_name": serializer.data["first_name"],
                        "last_name": serializer.data["last_name"],
                        "username": serializer.data["username"],
                        "email": serializer.data["email"],
                        "is_active": serializer.data["is_active"],
                        "is_staff": serializer.data["is_staff"],
                    },
                    "status": {
                        "message": "User created",
                        "code": f"{status.HTTP_200_OK} OK",
                    },
                    "token": token.key,
                }
            )
        return Response(
             {
                "error": serializer.errors,
                "status": f"{status.HTTP_203_NON_AUTHORITATIVE_INFORMATION} \ 
                    NON AUTHORITATIVE INFORMATION",
            }
        )
```

next, create the URL pattern and add the view responsible for handling request to the URL.

```python
# urls.py from django.urlsconf import path from authentication.views import RegistrationViewurls_patterns = [    #...    path("/api-register-auth", RegistrationView.as_view())]
```

Lastly, test the `RegistrationView` . It should return the appropriate response with a token if required and valid credentials are provided.

```bash
http --json POST http://localhost:8000/api/auth/register username="JamesChe" email="jamesuche@gmail.com" first_name="james" last_name="uche" password="jamesuche123456"{    "status": {        "code": "200 OK",        "message": "User created"    },    "token": "b4784f96c3c65387bc8ea6463d5d4658cb32b0ac",    "user": {        "id": 2,        "first_name": "james",        "last_name": "uche",        "username": "JamesChe",        "email": "jamesuche@gmail.com",        "is_active": true,        "is_staff" true,    }}
```

 

### Login

Implementing Login authentication using the REST framework is quite straightforward. There are two way to achieve setting up a login view: using built-in view or a custom  view.

#### Using `obtain_auth_token` view.

REST framework provides the built-in `obtain_auth_token` view for creating and retrieving tokens for authenticated user.

Clients can obtain token using registered credential. The *username* and *password* are what's required. Once the view confirms the credentials, It'll obtain the token if it exists or create a new one.

In the *urls.py* file, import as follows: `from rest_framework.authtoken.views import obtain_auth_token` then add the view to the URLconf.

```python
# urls.pyfrom rest_framework.authtoken.views import obtain_auth_tokenurl_patterns + [    path("/api-token-auth", obtain_auth_token.as_views())]
```

> <small><b>NOTE:</b></small>
>
> The naming of the URL pattern can be whatever you want.

To get token, the client have to send a post request to the URL. The client must provide the username and password of the registered user.

```bash
..authentication> http post http//localhost:800/api-token-auth/ username="johndoe" password=123456HTTP/1.1 200 OKAllow: OPTION, POSTSContent-Type: application/json......{  "token": "bad492c010451bcba6acf7437706b8dd30eb11d5"}
```

Remember the earlier `HelloView` view? Let's test it by sending a *GET* request to its URL, this time with the token we just got.

```bash
..authentication> http http://localhost:8000/api-hello "Authorization: Token bad492c010451bcba6acf7437706b8dd30eb11d5"HTTP/1.1 200 OKAllow: GET, HEAD, OPTIONS.........{    "message": "Hello, World!" # <------------ Message because user token is valid.}
```

And there! We're now able to access the view without restriction. the expected *Hello, World!* message is returned rather than a restriction message.

#### Using Custom View

Certain cases require extra data about the authenticated user be sent as response to the client. That "extra" data could be on a database. To retrieve such data, a *serializer* is needed to convert them to python native code format for easy serializing. The view will handle the necessary related logic and send response to the client.

Two serializer classes are needed for this: one to verify the user credentials and the other to return the necessary response.

```python
# Serializers.pyfrom django.contrib.auth.models import Userfrom rest_framework import serializersclass UserLoginSerializer(serializers.Serializer):    """Login serializer"""        username = serializers.CharField(required=True)    password = serializers.CharField(required=True, read_only=True)class UserLoginResponse(serializers.ModelSerializer):    """Response serializer"""        class Meta:        model = User        fields = "id, username, first_name, last_name, email, password, is_active, is_staff"        read_only_fields = ["id", "password", "is_active", "is_staff"]
```

Next, in the *views.py* file, the view has to check if the incoming data from the request is a valid data using the serializer. If it's not it'll return a serializer error. if the data is valid, the credentials will be authenticate and a response message with the generated or obtained token will be sent back to the client.

```python
# views.pyfrom django.contrib.auth.models import Userfrom authentication.serializers import LoginSerializerfrom authentication.serializers import LoginResponseSerializerfrom rest_framework import serializersfrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework import statusfrom rest_framework.authtoken.models import Tokenfrom rest_framework import permissionsfrom django.contrib.auth import authenticateclass LoginView(APIView):    """Login View"""        permissions_classes = [permissions.isAuthenticated]        def post(self, request, *args, **kwargs):        login_serializer = LoginSerializer(data=request.data)                if login_serializer.is_valid():            user = authenticate(request, **serializer.data)                        if user is not None:                response_class = LoginResponseSerializer(user)                token, created_token = Token.objects.get_or_create(user_id=user.id)                                if isinstance(created_token, Token):                    token = created_token.key                                return Response(                    {                        "user": response_serializer.data,                        "status": {                            "message": "User Authenticated",                            "code": f"{status.HTTP_200_OK} OK",                        },                        "token": token.key,                    }                )            else:                raise serializers.ValidationError(                {                    "error": {                        "message": "Invalid Username or Password. Please try again",                        "status": f"{status.HTTP_400_BAD_REQUEST} BAD REQUEST",                    }                }            )                return(            {                "error": serializer.errors,                "status": f"{status.HTTP_403_FORBIDDEN} FORBIDDEN"            }        )
```

Create URL pattern and add view to URLConf file.

```python
# urls.pyfrom django.urls import pathfrom authentication.views import LoginViewurlpatterns = [    #...    #...    path("/api-login-auth", views.LoginView),]
```

To test out the custom view, send a *POST* request with the required user credentials as JSON.

```bash
..authentication> http --json post http//localhost:800/api-login-auth username="johndoe" password=123456HTTP/1.1 200 OKAllow: OPTION, POSTS...Content-Type: application/json......{    "status": {        "code": "200 OK",        "message": "User Authenticated"    },    "token": "bad492c010451bcba6acf7437706b8dd30eb11d5",    "user": {        "email": "johndoe@gmail.com",        "first_name": "",        "id": 1,        "is_active": true,        "is_staff": true,        "last_name": "",        "username": "johndoe"    }}
```



## Conclusion

Implementing TokenAuthentication in Django REST framework can be steep at first. But it start to make sense when you understand the concept: Rather having a session on the server, a token is instead created and is used to verify the user on every request.

I recommend using the REST framework *APIView* classes-bases for easy work. Only when keep repeating yourself should you look into other class-based to reduce repetition.

If all the application needs is just the token, then use the built-in `obtain_auth_token` view for that.

Customs views may need extra data returned to the user, in this case you should create extra serializer classes to achieve that.

I'll appreciate your feedback on this post. Cheers!