How can I safely look at the request body in a Django REST framework Authentication?
<p>I've implemented a webhook. The caller provides a shared secret. They concatenate the request URL and body, sign it with the secret, and provide the signature in a header. To validate the signature, I need to repeat the process and compare the signature that I get to the one that the caller got.</p>

<p>I'm using Django REST framework. I'm checking the signature in a custom <code>Authentication</code>. The framework calls the <code>Authentication</code>'s <code>authenticate</code> method with the request as a parameter. I can get the request body from <code>request.stream.read()</code>. However, that consumes the stream, which prevents the framework from parsing it for me: when the framework calls the view the request has no <code>data</code> attribute as it normally would.</p>

<p><code>request.stream</code> doesn't allow me to <code>seek(0)</code>. The request doesn't allow me to replace the stream with a rewound one such as a <code>StringIO</code>. My current workaround is to store the body in <code>request.auth</code> and parse it myself in the view, which works but is ugly.</p>

<p>Is there a way to access the request body safely in an <code>Authentication</code>?</p>

<p>Here's the version that works:</p>

<p>In <code>myapp/authentications.py</code>:</p>

<pre><code>import hmac
from hashlib import sha1
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

class WebhookAuthentication(BaseAuthentication):
    def authenticate(self, request):
        actual_signature = request.META['X-Vendor-Signature']
        body, expected_signature = self.expected_signature(request)
        if actual_signature != expected_signature:
            raise AuthenticationFailed("Request X-Vendor-Signature did not match expected signature")
        return None, body  # Hack: preserve the body in request.auth

    def expected_signature(self, request):
        body = request.stream.read()
        raw = request.build_absolute_uri(request.META['PATH_INFO']) + body
        signature = hmac.new("the shared secret", raw, sha1).digest().encode("base64")
        return body, signature
</code></pre>

<p>In <code>myapp/views.py</code>:</p>

<pre><code>import json
from rest_framework.views import APIView    
from serializers import SomeSerializer
from myapp.authentications import WebhookAuthentication

class SomeView(APIView):
    authentication_classes = (WebhookAuthentication,)
    permission_classes = ()

    def post(self, request, _=None):
        data = json.loads(request.auth)  # Hack: get the body from where we stashed it
        serializer = SomeSerializer(data=data)  # I'd like to be able to use request.data here
        # Use the serializer ...
</code></pre>

<p>How can I do better than the hack?</p>