This library implements the HMAC Authentication protocol for the django-rest-framework.
This is useful because HMAC allows the client to authenticate and make an http request at the same time.
HMAC is used by the botocore Python library by AWS and other places around the web.
I know, I know...TL;DR.
This fork of the Django REST Framework HMAC library is modified from the original. In particular, I had some difficulties with with original. The following changes were made:
- The original version looked for a few headers in the client request, 'Signature', 'Timestamp', etc.. However, Django translates those header names to 'HTTP_SIGNATURE' and 'HTTP_TIMESTAMP', etc., so this library didn't seem to work (although the example project, which spoofs a request, did work). I changed the library to look for headers with the names that Django alters them to.
- The original version used a string timestamp as a "nonce." However, it didn't seem like the authentication was actually checking that the incoming timestamp was greater than the previous one (or even saving the previous one). I changed the timestamp to an integer nonce, and this nonce must increase with every new client request (the usual recommendation is to use the current POSIX timestamp in milliseconds as the nonce).
- HMAC authentication requires the client to create a signature that is unique to his request. The actual request itself (usually a JSON string) gets digested as part of this. In order to check that this signature is correct, the server must construct the signature from the exact same pieces that the client used, and compare signatures. The original version of the library received the client's request, but already parsed into a JSON object (by Django); the library then dumps this JSON object to a string in order to calculate the request signature. But imagine that the request JSON was a dictionary (e.g. {"firstname":"Bobby", "lastname":"Fischer"}). The client sends in this dictionary with the keys in some order, and the server converts this to a JSON object, which gets dumped back to a string (in order to compute the signature), but with the keys in potentially some other order. The signatures won't match. The answer is that both the client and server should treat their JSON in a canonicalized way: dictionary keys should be sorted, and separators should be ':' and ',' (with no spaces). This version of the library handles this canonicalization on the server side.
- Python 3.6
- Django 2.0+
- Django REST Framework 3.7
Install using pip
...
$ pip install django-rest-framework-hmac
from rest_framework.views import APIView
from rest_framework_hmac.authentication import HMACAuthentication
class MyView(APIView):
authentication_classes = (HMACAuthentication,)
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework_hmac.hmac_key.models import HMACKey
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_hmac_key(sender, instance=None, created=False, **kwargs):
if created:
HMACKey.objects.create(user=instance)
Use the above relationship between the Django User
and HMACKey
Model with the HMACSigner
convenience class to generate a HMAC signed request.
To successfully make a request to this View, the client must set 3 properties in the request header:
- Key
- Signature
- Nonce
The Key
is used to lookup the Django User
The Signature
is the HMAC Signature generated by signing with the HMAC cryptographic key
The Nonce
can be any integer turned into a string. With every new request, the nonce should
increase. Generally, one can typically use the POSIX timestamp in milliseconds.
import time, base64, hmac, requests, hashlib, json
api_key = '279ba11d13f5acc7b1eaa7fcddc62d633d1e47a7'
api_secret = '8432c4f3ce899b1e956760a4a11b1df234c8dd43'
source_addr = '127.0.0.1'
path = '/testhmac/'
REMOTE_URL = 'http://127.0.0.1:8000' + path
payload = {'counter': 60}
payloadStr = json.dumps(payload, separators=(',', ':'), sort_keys=True) # canonicalize the request
nonce = int(time.time() * 1000)
params = {'method':'POST', 'hostname':source_addr, 'path':path, 'nonce':nonce, 'payload': payloadStr}
strToSign = '{method}\n{hostname}\n{path}\n{nonce}\n{payload}'.format(**params)
secret_bytes = bytes.fromhex(api_secret)
lhmac = hmac.new(secret_bytes, digestmod=hashlib.sha256)
lhmac.update(strToSign.encode('utf8'))
sig = base64.b64encode(lhmac.digest())
headers = {'KEY': api_key, 'Signature': sig, 'Nonce': '{nonce}'.format(nonce=nonce), 'content-type': 'application/json'}
r = requests.post(REMOTE_URL, headers=headers, data=payloadStr)
print('response:', r.content)
To run the example project, run the following commands
cd rest_framework_hmac/example_project/
# create virtualenv (python3 must be installed)
python3 -m venv venv
# activate virtualenv
source venv/bin/activate
# install requirements
pip install -r requirements.txt
cd example
# run tests
./manage.py test
# or run Django local server
./manage.py runserver
BSD 2-Clause