Build API Key Authentication in your Django REST application: Django, Django REST, Simple API Key & Docker
Tags: python, django
Published: 2023-02-26 16:25:43

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**](https://github.com/koladev32/djangorestframework-simple-apikey) package\*\*.\*\*

## Setup project

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

```bash
python3.11 -m venv venv
```

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

```bash
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.

```bash
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.

```python
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.

```python
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.

```bash
python manage.py generate_fernet_key
```

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

```bash
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](https://dev.to/koladev/django-rest-authentication-cmh)

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

![Tables structure](https://cdn.hashnode.com/res/hashnode/image/upload/v1677421439025/d25c2758-4cfb-4379-824e-80a89b01e7d9.png)

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

| Resource | Method | Route | Authentication |
| --- | --- | --- | --- |
| Create an API key for an organization | POST | /api/organization/create\_api\_key/ | None |
| Create a payment | POST | /api/payment/ | API Key |
| List payments for an organization | GET | /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`.

```bash
django-admin startapp organization
```

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

```python
# 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.

```python
# 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](https://djangorestframework-simple-apikey.readthedocs.io/en/latest/customizing_api_key_model.html) for custom behaviors.

```python
# 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.

```python
# 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.

```python
# 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`.

```python
# ./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.

```python
# 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.

```bash
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.

```bash
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.](https://cdn.hashnode.com/res/hashnode/image/upload/v1677423828651/a8b6f77a-2ba7-4e32-a37b-d036c0152b56.png)

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`.

```bash
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.

```python
# 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 the`Payment` 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.

```python
# 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](https://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield).

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.

```python
# 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.

```python
# 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
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 👇‍

```json
Authorization: ApiKey <your-api-key>
```

![Creating a payment](https://cdn.hashnode.com/res/hashnode/image/upload/v1677425697627/c59efdab-e75c-4676-8692-8a170b440009.png)

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](https://docs.docker.com/desktop/install/windows-install/) 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.

```bash
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 👇‍

```bash
docker build -t django-api-key .

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

The project will start running at http://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](https://github.com/koladev32/django-rest-apikey-auth).

*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](https://packt.link/a/9781803242972) to order your copy now!"*