[SOLVED] GitHub Webhooks & Django - authentification help please!

Hi there!

Hi everyone - I was wondering if someone could help with securing a GitHub webhook request in Django.

The majority of the guides I’ve found for this (such as this: Handling GitHub webhooks with Django · GitHub) generate the “signature” by using request.body, from the webhook’s request itself. However, in all of my cases request.body is empty!

Please help a poor dev who is tired and just wants his webhooks to be secure! My code below:

@csrf_exempt
def webhook_update(request):
    # AUTH: https://gist.github.com/grantmcconnaughey/6169d8b7a2e770e85c5617bc80ed00a9
    if not "X-Hub-Signature" in request.headers:
        return HttpResponseForbidden("Invalid")

    github_signature = request.META["HTTP_X_HUB_SIGNATURE"]
    signature = hmac.new(os.getenv("SECRET_TOKEN"), request.body, hashlib.sha1)
    expected_signature = "sha1=" + signature.hexdigest()

    if not hmac.compare_digest(github_signature, expected_signature):
        return HttpResponseForbidden("Invalid signature header")

Confusingly the sample I reference above highlights that the “payload” could be in request.body or in request.POST[‘payload’], but this is AFTER the signature variable has been generated from the hmac.new() statement, so I wanted to ask around before blindly experimenting.

    # Sometimes the payload comes in as the request body, sometimes it comes in
    # as a POST parameter. This will handle either case.
    if 'payload' in request.POST:
        payload = json.loads(request.POST['payload'])
    else:
        payload = json.loads(request.body)

GitHub docs on securing webhooks: Securing your webhooks - GitHub Docs

TIA!

Rich

It’s been a while since I have used it, but I have had a project running stable for 3 or 4 years using django-github-webhook.

You might check out: django-github-webhook/views.py at master · fladi/django-github-webhook · GitHub and see if payload = json.loads(request.body.decode('utf-8')) is really what you want. That implies that you should be getting a JSON-friendly string back that just needs to be loads up as a dict.

1 Like

Thanks @jeff!

Looking into the (admittedly FAR more robust!) code of the django-github-webhook package I cobbled this together:

@csrf_exempt
def webhook_update(request):
    # AUTH: https://gist.github.com/grantmcconnaughey/6169d8b7a2e770e85c5617bc80ed00a9
    if not "X-Hub-Signature" in request.headers:
        return HttpResponseForbidden("Invalid")

    # from  django-github-webhook
    digest_name, signature = request.META["HTTP_X_HUB_SIGNATURE"].split("=")
    
    if digest_name != "sha1":
        return HttpResponseBadRequest(f"Unsupported X-HUB-SIGNATURE digest mode found: {digest_name}")

    secret_token = os.getenv("SECRET_TOKEN")

    mac = hmac.new(secret_token.encode("utf-8"), msg=request.body, digestmod=hashlib.sha1)

    if not hmac.compare_digest(mac.hexdigest(), signature):
        return HttpResponseBadRequest("Invalid X-HUB-SIGNATURE header found")

At present I’m just happy it works but I’m genuinely surprised that msg=request.body is working in this context but didn’t work in my original code, but diagnosing that is for another day.

It should be noted that having made my own version I will most likely be moving to actually use the django-github-webhook package instead as it covers A LOT more authentification that my code, but it was a great learning exercises regardless!

Cheers!

Rich

1 Like