How to build an API with authentication using Django

Django is a popular web framework for Python that provides many features to create web applications. One of these features is the Django REST framework, which is a powerful and flexible toolkit for building web APIs. In this blog post, we will learn how to use Django REST framework to create a simple API that can perform CRUD (Create, Read, Update, and Delete) operations on an entity. We will also add authentication to our API, so that only authorized users can access and modify the data.

What is Django REST framework?

Django REST framework is an extension of Django that provides a set of tools and libraries to build web APIs. Django REST framework makes it easy to:

  • Define the data models and serializers for the API
  • Create the views and routers for the API endpoints
  • Add authentication and permissions to the API
  • Test and document the API
  • Customize and extend the API functionality

Django REST framework follows the RESTful principles and supports various data formats, such as JSON, XML, YAML, etc. It also provides a browsable API interface that allows users to explore and interact with the API directly from the web browser.

How to install Django and Django REST framework?

To use Django and Django REST framework, we need to have Python installed on our system. We can check the Python version by running the following command in the terminal:

python --version

We also need to create a virtual environment to isolate the dependencies of our project. We can use the venv module to create and activate a virtual environment:

python -m venv env
source env/bin/activate

Next, we need to install Django and Django REST framework using the pip command:

pip install django
pip install djangorestframework

We can verify the installation by running the following command:

pip freeze

This should show the versions of Django and Django REST framework that we have installed.

How to create a Django project and app?

To start a new Django project, we can use the django-admin command:

django-admin startproject api_project

This will create a folder called api_project with the following structure:

api_project/
    manage.py
    api_project/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py

The manage.py file is a utility script that allows us to run various commands for our project, such as creating apps, running the server, migrating the database, etc. The api_project folder contains the configuration files for our project, such as settings.pyurls.pyasgi.py, and wsgi.py.

To create a new app for our project, we can use the startapp command:

python manage.py startapp api_app

This will create a folder called api_app with the following structure:

api_app/
    __init__.py
    admin.py
    apps.py
    models.py
    tests.py
    views.py
    migrations/
        __init__.py

The api_app folder contains the files for our app, such as models.pyviews.pyadmin.py, etc. The migrations folder contains the files for the database migrations, which are the changes that we make to our data models.

To register our app with our project, we need to add api_app and rest_framework to the INSTALLED_APPS list in the settings.py file:

# settings.py

INSTALLED_APPS = [
    # ...
    'api_app',
    'rest_framework',
]

We also need to include the api_app URLs in the urls.py file of our project:

# urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api_app.urls')),
]

We use the include function to delegate the URL patterns to the api_app app. We also prefix the api_app URLs with api/, so that they are distinct from the other URLs of our project.

How to define the data model and serializer for the API?

The data model is the representation of the data that we want to store and manipulate in our API. The data model defines the properties and relationships of the entities that we want to expose through the API. In Django, we can define the data model using the models module, which provides various classes and fields to create the database tables and columns.

For this blog post, we will create a simple data model for a Book entity, which has the following properties:

  • title: The title of the book
  • author: The author of the book
  • description: A brief description of the book
  • published: The date when the book was published

We can define the Book model in the models.py file of our app:

# models.py

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=100)
    description = models.TextField()
    published = models.DateField()

    def __str__(self):
        return self.title

We use the models.Model class as the base class for our model, and we use various field classes to define the attributes of our model. We also define a __str__ method to return a human-readable representation of our model.

To create the database table for our model, we need to run the following commands:

python manage.py makemigrations
python manage.py migrate

The makemigrations command will generate a migration file in the migrations folder, which contains the SQL commands to create the table and columns for our model. The migrate command will execute the migration file and apply the changes to the database.

The serializer is the component that converts the data model to and from a format that can be transmitted over the network, such as JSON or XML. The serializer also validates the data and handles the errors. In Django REST framework, we can define the serializer using the serializers module, which provides various classes and fields to create the serializer.

We can define the BookSerializer in the serializers.py file of our app:

# serializers.py

from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

We use the serializers.ModelSerializer class as the base class for our serializer, and we specify the model and the fields that we want to include in the serializer. We use the __all__ value to include all the fields of the model. We can also customize the serializer by adding or overriding the fields and methods.

How to create the views and routers for the API endpoints?

The views are the components that handle the requests and responses for the API endpoints. The views define the logic and behavior of the API, such as what data to return, what actions to perform, what status codes to send, etc. In Django REST framework, we can create the views using the views module, which provides various classes and mixins to create the views.

We can create the BookViewSet in the views.py file of our app:

# urls.py

from django.urls import path, include
from rest_framework import routers
from .views import BookViewSet

router = routers.DefaultRouter()
router.register('books', BookViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

We use the routers.DefaultRouter class to create a default router, and we use the register method to register the BookViewSet with the router. The router will automatically generate the URL patterns for the BookViewSet, such as:

  • api/books/: The list and create endpoint for the books
  • api/books/<id>/: The retrieve, update, and destroy endpoint for a specific book

We can also customize the router by adding or overriding the methods and attributes.

How to add authentication and permissions to the API?

Authentication is the process of verifying the identity of the user who is accessing the API. Authentication ensures that only authorized users can access the data and perform the actions that they are allowed to do. Permissions are the rules that determine what actions the user can perform on the data. Permissions ensure that the user can only access and modify the data that they are authorized to do.

In Django REST framework, we can add authentication and permissions to the API using the authentication and permissions modules, which provide various classes and methods to implement the authentication and permissions.

For this blog post, we will use the TokenAuthentication and the IsAuthenticated classes to add authentication and permissions to our API. The TokenAuthentication class is a simple authentication scheme that uses a token to identify the user. The IsAuthenticated class is a simple permission class that only allows access to authenticated users.

To use the TokenAuthentication class, we need to install the rest_framework.authtoken app and run the migrations:

pip install djangorestframework.authtoken
# settings.py

INSTALLED_APPS = [
    # ...
    'rest_framework.authtoken',
]
python manage.py makemigrations
python manage.py migrate

The rest_framework.authtoken app will create a table called authtoken_token in the database, which will store the tokens for the users. The tokens are randomly generated strings that are assigned to the users when they register or log in.

To use the IsAuthenticated class, we need to add it to the permission_classes attribute of our view:

# views.py

from rest_framework import viewsets, permissions
from .models import Book
from .serializers import BookSerializer

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = [permissions.IsAuthenticated]

The permission_classes attribute is a list of permission classes that are applied to the view. The IsAuthenticated class will check if the user is authenticated before allowing access to the view. If the user is not authenticated, the view will return a 401 Unauthorized response.

To test the authentication and permissions of our API, we need to obtain a token for the user and include it in the request header. We can use the obtain_auth_token view provided by the rest_framework.authtoken app to get a token for the user:

curl -X POST http://localhost:8000/api-token-auth/ -d "username=admin&password=admin"

This will return a JSON response with the token for the user:

{"token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"}

We can then use the token to access the API endpoints by adding the Authorization header to the request:

curl -X GET http://localhost:8000/api/books/ -H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"

This will return a JSON response with the list of books:

JSON

[
    {
        "id": 1,
        "title": "The Hitchhiker's Guide to the Galaxy",
        "author": "Douglas Adams",
        "description": "A classic science fiction comedy novel",
        "published": "1979-10-12"
    },
    {
        "id": 2,
        "title": "The Lord of the Rings",
        "author": "J.R.R. Tolkien",
        "description": "An epic fantasy adventure trilogy",
        "published": "1954-07-29"
    }
]

If we try to access the API endpoints without the token, or with an invalid token, we will get a 401 Unauthorized response:

curl -X GET http://localhost:8000/api/books/
{"detail": "Authentication credentials were not provided."}