API request signing in Django
This article is intended to give you insights in request signing and how to implement it in the Django Framework. This article assumes familiarity with Python/Django.
Side note: server and back-end are used interchangeably in this article.
The story begins when an attacker starts sniffing your app requests; then they know all your API endpoints and structure. No harm, right? Let’s see.
Since this attacker person is more creative than you, they created a better UI for the app and might even get better user engagement in their app compared to yours.
Your users start using this “better” app, thinking it is from your company. Meanwhile the attacker steals your user data!
What should you do?
You can monitor the requests coming from this app and block them: Good. Meanwhile he stole the credentials of 1,000 accounts. Also, the requests may be coming from a server already and then they can mutate how to send the requests and BOOM, their app is working again!
This might seem to be an imaginary scenario for you, as it is not especially for a novice hacker, but let’s imagine a simpler scenario.
You have a public endpoint that can be exposed by anyone for login, logout and two other endpoints. The attacker can use those endpoints, not necessary with a correct body format, to implement DOS or DDoS attack since you keep processing this as a normal request.
I believe that by now, you know what the problem is. Exactly, you want a way to authenticate the source that sends the requests. A way to be sure that this requests come from some app or agent that you trust. To summarize, we need to implement a MAC (Message Authentication Code), an algorithm which confirms that a given message came from a trusted source and that the data in the payload has not been altered in between.
Usually, implementing of MAC derivatives in the request is called API Signing.
The idea is simple:
Compute a Hash that no one can compute except of the Server and the Consumer.
Server will recompute the hash with each request and compare it with the Hash sent by the consumer.
This Hash is what we are referring to be the Request Signature.
API Signing Benefits
- Identity Verification: You are sure that the request is coming from who you are expecting.
- You are sure that the message is not altered in communication channels.
- You can prevent relay attack (optional).
Remember those three benefits, later we can check if we achieved them or not!
API Signing Logic and Workflow
The backend should build the request signature checker and communicate the way of request signing to interested parties along with a secret code for each party (later referred to as consumer). The secret code must be communicated through a secure channel between back-end and consumer.
The simple way to do this, is to show it in the consumer (user/web-app/mobile app) profile where no one can reach this page without consumer (user/web-app/mobile app) credentials.
What should the back-end give to the consumer?
The back-end should specify the following:
- Parameters used to construct the message that will be hashed.
- The hashing algorithm.
- How to pass this in the request (Special header, URL Query, Body)
What should the consumer do?
The consumer should do the following:
- Construct the message using the parameters decided by the Server.
- Hash the message with the algorithm using the secret key.
- Send the request along with this signature.
API Signing using Django
The best direct way is to implement API Request Signing in a middleware.
For demonstration simplicity, I’ve assumed the following:
- You have only one consumer with one secret key. In a lot of projects, we only need this to protect your the to be used from another consumer. This can be true for most cases.
- Secret key is already known by the consumer.
Inside one of the apps, create a module
from base64 import b64encode
from hashlib import sha256
from django.conf import settings
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if not self.is_valid(request):
def is_valid(self, request):
"""Validate signed requests."""
api_signature = request.META.get('HTTP_SIGNATURE')
secret = settings.SECRET_KEY
params = [
data = "-".join(params)
data = data.encode('utf-8')
computed_sig = hmac.new(
signature = b64encode(computed_sig).decode()
return signature == api_signature
Now, let’s explain what this code is trying to achieve. To understand how the middleware is working in Django, please read about this topic on the Django project website.
__call__ method will be called with every request, so in every request we check if the request is valid or not. How? by computing the signature (hash) and see if it is the same as the one sent by the consumer.
Pseudo code could be:
1. Read a signature from header.
2. Construct the message. In the example, it consists of
* Secret Key
* Request Method (PUT, GET, POST, ..)
* URL Endpoint.
* Request Body.
those parameters are concatenated and separated by - symbol.
3. Hash it with SHA256 using the Secret Key.
No one knows how to construct this message except a consumer with a correct secret key. Anyone else who wants to use this API, will end up with an unauthorized response.
Recap of the benefits
- Identity Verification: Achieved (Since both secret key and message construction are unknown to the public)
- Correctness of the message sent: Achieved (Since request body is part of message construction)
- Prevent Relay attack: Can be achieved easily if you add the timestamp in the data sent to the server and include it in the hash computation. If the server detects that the timestamp is too old, it can discard the request.
Implementing API signing is important to secure your back-end, but indeed, it is not solving all your security issues.
What is next?
One of the biggest challenges is how to rotate the secret key and change it frequently. In a next blog post, I’ll show you how to integrate the request signing with DRF and take a look of a different options to rotate the secret key. Check it here (Part 2).