Tracking API calls by User in Django (Python)

Tracking API calls by anonymous usage is only part of the story when it comes to leveraging API analytics and metrics. A lot more value can come from knowing exactly what users are using specific endpoints and how they are using them.

In order to track API calls and attribute them to a user, the API calls must be authenticated. For this particular example, I’m going to use a JSON Web Token, also referred to as a “JWT”. The JWT should be attached to the request. The specific field we will use to track the user is the JWTs id field.

The code I am using is built from an official Django tutorial. It also includes the integration configuration I went over in the previous Django integration tutorial. All of the steps in this tutorial are performed against that codebase.

To get user tracking working I will:

  • Configure my Moesif middleware in my Django API server to extract the user ID coming from the JWT
  • Configure my endpoints to accept a JWT within the request
  • Generate a JWT
  • Attach a JWT to my request, and send it to the API

A few things to mention in this guide, before we start.

  1. I will be generating a JWT from an endpoint within the REST service I have built. From a security perspective, it is not meant to be production-ready but merely a demonstration of how to get things started.

  2. Our example assumes that your server and API are already integrated with Moesif. If you still need to integrate your API with Moesif, please take a look at our Django integration guide before proceeding if you want to follow along.

With that said, let’s jump into our example!

The starting point

You’ll notice that the User ID field is blank. This means that these events are not tagged to a specific user. Therefore, we cannot leverage user-specific metrics.

Moesif Metrics screen requests without a userID

If you don’t see any traffic coming into your Moesif instance, ensure that you have set up the integration correctly.

Adding user tracking to our Moesif middleware

In order to add user tracking to our configuration, we need to supply an identifyUser parameter to the Moesif middleware configuration. In our settings.py file, our Moesif configuration will now look like this:

settings.py

def identifyUser(req, res):
    if req.user and req.user.is_authenticated:
        return req.user.username
    else:
        return None

MOESIF_MIDDLEWARE = {
    'APPLICATION_ID': 'MOESIF APPLICATION_ID',
    'IDENTIFY_USER': identifyUser # Optional hook to link API calls to users
}

This identifyUser function will parse out the field from the request you want to use to identify the user. The decoded JWT payload is available on the request via the user property.

NOTE: you don’t have to do follow this exactly. You could pull the unique identifier from any part of the request, including the header, if needed. I am following this convention as it has become a standard way of passing a user’s ID through a JWT. The only thing necessary is to have the users ID be a unique and unchanging value to ensure accurate metrics.

With the code we supplied, Moesif will now be able to attach a user ID to an API request when the middleware intercepts the request.

We now need to configure our endpoints to use the JWT. For that, we will use a package which will give us the JWT functionality we need. You can use another if you’d prefer but I will use the Django REST Framework JWT package.

Adding in the djangorestframework-jwt dependency

To add in the djangorestframework-jwt dependency, run the following pip install command in your terminal:

pip install djangorestframework-jwt

NOTE: Make sure to use the correct pip command in the correct directory. If you’re using a new version of Python, you may want to use pip2, or pip3 instead of the above pip command.

Once this command completes, the djangorestframework-jwt package will be added to your project and ready to use. Now we can begin to plug it into our code.

NOTE: The library we are using needs an older PyJWT dependency to work. An error may appear.

PyJWT version error

To get the application to work as expected, I downgraded to PyJWT version 1.7.1. To do this, you can run:

pip install PyJWT==1.7.1

Using djangorestframework-jwt in the code

In our settings.py file, we will add the following DEFAULT_AUTHENTICATION_CLASSES entry to our REST_FRAMEWORK list, like so:

settings.py

REST_FRAMEWORK = {
    ....
    ....
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}

Now, in our urls.py file, we will import the package we will use for the JWT auth:

urls.py

from rest_framework_jwt.views import obtain_jwt_token

We will also add in a path for /api-token-auth to our urlpatterns list so we can obtain a JWT. That route will look like this:

urls.py

urlpatterns = [
   
   
   path('api-token-auth/', obtain_jwt_token)
]

Now to ensure our endpoint will enforce the incoming request to have a valid JWT attached to it, we need to go into our views.py and ensure that we are enforcing authneitcation for our views. Permission_classes should equal permissions.IsAuthenticated. The code will now look like this:

views.py

class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all().order_by('-date_joined')
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]

Our next steps are to generate our JWT to attach to our request.

Creating the JWT

In this example, I will be generating the JWT using the /api-token-auth endpoint that we created before in our urls.py file.

In this case, I have a user I’ve created using the following command:

python manage.py createsuperuser --email admin@example.com --username admin

I will use this users credentials to generate the JWT to give me access to the /users endpoint.

After starting my server, I will then navigate to the /api-token-auth endpoint (my url for this is http://localhost:8000/api-token-auth/)

example text

From here, I will add in my username (“admin”) and the password I input for this user. I’ll then click POST and be presented with a token to use.

example text

Now, I’ll attach the JWT to the request in Postman.

Sending a request through Postman

After opening up Postman, we will plug in the endpoint URL for our GET request. We will also add an Authorization header with a value of “JWT my_jwt”, where my_jwt is replaced with the generated JWT from the previous step. Here’s an example below:

example text

Now, send off the request. At this point, the request will be received by the server. Once at the server, the following will occur: the JWT field you want to use as an ID for the call will be parsed out using the Moesif middlewares identifyUser function and attached to the metric in Moesif.

Verifying our request went through and shows our user

In Moesif, our request should be showing in the Metrics dashboard. Now we can see that a userID has been bound to the request.

You’ll see that the id passed in our JWT has now become the userID attached to the request we sent.

example text

At this point, we are now able to start leveraging user-specific metrics instead of having all of our data be anonymous. This can help to power many of the more advanced and valuable features within Moesif.

Updated: