Build API Key Authentication in your Django REST application: Django, Django REST, Simple API Key, Docker

ยท

9 min read

Depending on the needs of a web application you are working on, you might want to implement ApiKey authentication if you want certain entities to access some of your API resources. API authentication is great for the following cases:

  1. Rate limiting: By requiring an API key, you can track and limit the number of requests made to their API by a single user or application.

  2. Access control: API keys can restrict access to certain parts of an API, allowing you to control which endpoints and resources are available to different entities.

  3. Monetization: API keys can be used to track usage and bill users based on the number of requests made to an API or the amount of data consumed.

If your needs for an authentication system don't fit the cases above, it is better to directly go with user authentication.

In this article, we will build a REST API that uses ApiKey authentication under the hood with the simple-apikey package**.**

Setup project

The first step is to create a virtual environment. I am working with Python3.11 for this article.

python3.11 -m venv venv

After creating the virtual environment, let's install Django and the required packages for the project.

pip install django djangorestframework-simple-apikey

This will install Django, Django REST framework, and the Django REST Framework Simple ApiKey package.

After that, create a new Django project.

django-admin startproject ApiKey .

A new project will be created. We then need to add a required configuration for the simple API key package. Let's register the rest_framework and the rest_framework_simple_api_key application in the INSTALLED_APPS in the settings.py file.

INSTALLED_APPS = [
    ...
    "rest_framework",
    "rest_framework_simple_api_key",
]

In the settings.py file of the project, add the following line at the end of the project.

SIMPLE_API_KEY = {
    "FERNET_SECRET": os.environ.get("FERNET_SECRET"),
}

It is a great habit to not have important keys written in plain text in the code. That is why we will be using env variables. The next step is to generate a fernet key and store it inside a .env file.

python manage.py generate_fernet_key

Store the output inside the .env file following this structure.

FERNET_SECRET=YOUR_GENERATED_FERNET_KEY

The project is ready and we can start coding.

Building a mini fintech project

The project of this article is quite simple. We will allow organizations to create payments. The request made to create a payment, list, or retrieve a payment are protected by Api Key authentication.

For the sake of simplicity, we will have a simple design for the tables and the resources of the API we are going to build. Thus, we won't add user authentication when we have to request to create an Api Key for an organization. I suppose that you have enough knowledge on how to implement user authentication, so we can focus on the API Key authentication in this article. :)

Note: If you want to learn more about JWT authentication with Django REST, you can read an article I have written about it here. It is a full-stack article with React, but the first part of the article focuses on Django and Django REST only.

https://dev.to/koladev/django-rest-authentication-cmh

Here is the structure of the Organization and the Payment models.

Tables structure

And here are the routes for the API resources and their authentication system.

ResourceMethodRouteAuthentication
Create an API key for an organizationPOST/api/organization/create_api_key/None
Create a paymentPOST/api/payment/API Key
List payments for an organizationGET/api/payment/API Key

Let's start writing the API by creating the organization application first.

Organization application

Inside the Django project, run the following command to create a Django app called organization.

django-admin startapp organization

Once the application is created, register it in the INSTALLED_APPS in the settings.py file.

# ApiKey/settings.py
INSTALLED_APPS = [
    ...
    "rest_framework_simple_api_key",
    # my apps
    "organization",
]

After that, we can now write the Organization model in the organization/models.py file.

# organization/models.py
from django.db import models


class Organization(models.Model):
    name = models.CharField(max_length=255)
    created = models.DateTimeField(auto_now_add=True)
    is_active = models.BooleanField(default=True)

Let's also add a model that will extend the Api Key model used for authentication as recommended in the official documentation for custom behaviors.

# organization/models.py
...

from rest_framework_simple_api_key.models import AbstractAPIKey

...

class OrganizationAPIKey(AbstractAPIKey):
    entity = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE,
        related_name="api_keys",
    )

After that, create a file called backends.py in the directory of the organization app. This file will contain the code for a custom authentication backend. We just need to tell the ApiAuthentication backend which model to use for authentication now.

# organization/backends.py

from rest_framework_simple_api_key.backends import APIKeyAuthentication
from organization.models import OrganizationAPIKey


class OrganizationAPIKeyAuthentication(APIKeyAuthentication):
    model = OrganizationAPIKey

Then, create a file called viewsets.py in the directory of the organization app. This file will contain the code for the OrganizationViewSet class which will just contain an action to create a new API key.

# organization/viewsets.py

from rest_framework import viewsets

from rest_framework.decorators import action

from rest_framework.response import Response
from rest_framework import status

from organization.models import OrganizationAPIKey, Organization


class OrganizationViewSet(viewsets.ModelViewSet):
    http_method_names = ["post"]
    queryset = Organization.objects.all()

    @action(methods=["post"], detail=True)
    def create_api_key(self, request, *args, **kwargs):
        organization = self.get_object()
        _, key = OrganizationAPIKey.objects.create_api_key(
            name="Org Api Key", entity=organization
        )

        return Response({"key": key}, status=status.HTTP_201_CREATED)

With the viewset added, we can register it using Django REST routers. At the root of the Django project, create a file called routers.py.

# ./routers.py

from rest_framework import routers

from organization.viewsets import OrganizationViewSet

router = routers.SimpleRouter()
router.register(r"organization", OrganizationViewSet, basename="organization")

urlpatterns = router.urls

And in the urls.py file of the Django project, register the routes of the router.

# ApiKey/urls.py

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

urlpatterns = [
    # path("admin/", admin.site.urls),
    path("api/", include(("routers", "api"), namespace="api")),
]

Great! We can now create migrations, run the migrations, and then start creating API keys for an organization.

python manage.py makemigrations
python manage.py migrate

python manage.py runserver

Before creating an API key, we need to have an organization in the database first.

Run the following command to open the Django shell and quickly create an organization.

python manage.py shell

> from organization.models import Organization
> o = Organization(name="Stripe Alike")
> o.save()

I assume that it is the first organization you are creating if you are following this article, so the pk of the organization will be 1. Let's make a POST request http://localhost:8000/api/organization/1/create_api_keys/ to create an API key.

Creating an API Key.

Save the value of the API Key somewhere as it will be used for requests on the Payments resources. Let's craft the application for the payment resource now.

Blazing fast payments

At the root of the Django project, run the following command to create an application called payment.

python manage.py startapp payment

Once the app is created, register it in the INSTALLED_APPS setting in the settings.py file of the project.

After that, let's write the Payment model.

# payment/models.py
from django.db import models


class Payment(models.Model):
    amount = models.FloatField()
    organization = models.ForeignKey(
        "organization.Organization", on_delete=models.CASCADE
    )
    description = models.TextField(max_length=1000)

With the model created, we can write a serializer class for thePayment model. Inside the directory of the payment application, create a file called serializers.py file. This file will contain the code for the PaymentSerializer class.

# payment/serializers.py
from rest_framework import serializers

from organization.models import Organization
from payments.models import Payment


class PaymentSerializer(serializers.ModelSerializer):
    organization = serializers.PrimaryKeyRelatedField(
        queryset=Organization.objects.all()
    )

    class Meta:
        model = Payment
        fields = "__all__"

Note: The serializers.PrimaryKeyRelatedField is used when you want to represent a relationship between two models using primary keys. You can learn more here.

Next, we can add the viewsets for the payment app and this is where it becomes easy and interesting. Create a file called viewsets.py in the directory of the payment app.

# payment/viewsets.py
from rest_framework import viewsets
from rest_framework_simple_api_key.permissions import IsActiveEntity

from organization.backend import OrganizationAPIKeyAuthentication
from payments.models import Payment
from payments.serializers import PaymentSerializer


class PaymentViewSet(viewsets.ModelViewSet):
    http_method_names = ["get", "post"]
    authentication_classes = (OrganizationAPIKeyAuthentication,)
    permission_classes = (IsActiveEntity,)
    serializer_class = PaymentSerializer

    def get_queryset(self):

        return Payment.objects.filter(organization=self.request.user)

For the authentication_classes, we are using the custom OrganizationAPIKeyAuthentication backend. This will ensure that every request on this endpoint requires a valid API Key.

After that, we also have the permission_classes attribute which checks if the entity or proprietary of the API key is active. This class requires the entity model (in our case Organization) to have the is_active attribute, otherwise, you might encounter some bugs.

As a complete authentication system and following the Django way of doing authentication, the request.user is set to the entity, in our case an organization. We can then for example filter payments according to the organization asking for them.

We can register the PaymetViewSet in the routers.py file now.

# routers.py

from rest_framework import routers

...
from payments.viewsets import PaymentViewSet
...
router.register(r"payment", PaymentViewSet, basename="payment")

urlpatterns = router.urls

Great! Let's run the migrations commands and create payments.

python manage.py makemigrations
python manage.py migrate

Use your favorite API Client to make requests. I am using Insomnia actually and the Authorization header is defined as follows ๐Ÿ‘‡โ€

Authorization: ApiKey <your-api-key>

Creating a payment

Great ๐Ÿš€! You have just learned how to build an API key authentication system using Django, Django REST, and Django REST Framework Simple API Key ๐Ÿ” package.

As a bonus, I am adding a small section on how to dockerize the application we have created.

Dockerizing the application

Ensure that you have Docker installed on your machine. Once it is done, create a file called Dockerfile at the root of the project. This file will describe the steps needed to create an image that will be used by Docker to successfully run the project above in a container.

FROM python:3.11-alpine

WORKDIR app/

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1


# install python dependencies
COPY requirements.txt /app/requirements.txt
RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt

# run migrations

RUN pip install --no-cache-dir -r requirements.txt

COPY . /app

EXPOSE 8000

CMD ["python", "manage.py", "migrate"]
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

You can then build and run the Dockerfile with the following commands ๐Ÿ‘‡โ€

docker build -t django-api-key .

docker container run -p 8000:8000 django-api-key --env-file=.env

The project will start running at localhost:8000. ๐Ÿš€

Conclusion

I hoped this article was helpful for you and it can get you started with API key authentication in your Django REST projects. If you think that this article could have been made better, feel free to comment below.

Also, you can check the code source of the project of this article on GitHub.

Ready to take your Django and React skills to the next level? Check out my book, Full Stack Django and React: Get hands-on experience in full-stack web development with Python, React, and AWS, for an in-depth guide to building web applications with these powerful tools. Whether you're a beginner or an experienced developer, you'll find practical tips and insights to help you succeed. Click here to order your copy now!"