Skip to content

nickc92/django-rest-framework-hmac

 
 

Repository files navigation

Django REST Framework HMAC Authentication

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.

Changes from the original version of the library

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.

Requirements

  • Python 3.6
  • Django 2.0+
  • Django REST Framework 3.7

Installation

Install using pip...

$ pip install django-rest-framework-hmac

Documentation

How to use

Define a View with HMACAuthentication set in the authentication_classes

from rest_framework.views import APIView
from rest_framework_hmac.authentication import HMACAuthentication

class MyView(APIView):
    authentication_classes = (HMACAuthentication,)

Register the HMACKey Signal, so a HMAC key and secret get generated each time a User is created.

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.

Example client code:

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)

Example project

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

License

BSD 2-Clause

About

HMAC Authentication for the django-rest-framework

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Python 100.0%