Django returns 403, but only from iphone browsers and only when a photo is uploaded

I'm getting a status 403 from my django server, but only in very specific conditions:

  • An image is attached to the form.

  • The request is sent from an Iphone.

Only when these 2 conditions are met does the server return 403. If an Iphone is used, but no photo is attached to the form, no errors. If a photo is attached to the form, but an android phone is used, no errors. There are 2 different forms that can take photos, both of these forms are having the same behaviour.

The CSRF token is dynamically added to all forms using js. Adding a photo to the form does not alter the CSRF hidden input in any way. The server log shows this:

WARNING [django.security.csrf:248] Forbidden (CSRF token missing.)

Again, this is strictly when those specific conditions are met. The CSRF token is confirmed to be in the POST data. The exact same form with the exact same data and the exact same CSRF, but without the photo, will submit successfully.

There is a service worker handling the POST requests. It doesn't alter the POST data (at least not intentionally). The photo upload used to work on every device/browser. The error started happening when the service worker was implemented.

The POST request handling function is this:

function HandlePOST(event){ // POST
  console.log('>>> Handling POST');
  event.respondWith(
    fetch(event.request).then((fetchedResponse) => {
      PrintNetworkResponse(fetchedResponse);
      return fetchedResponse
    }).catch(() => {
      const req_url = String(event.request.url);
      if(req_url.includes('/specific/post/')){
        return PathFromCache("/previous/page/")
      }else if(req_url.includes('/specific/post/2')){
        return PathFromCache("/previous/page/2")
      }else if(req_url.includes('/specific/post/3')){
        const get_req = POSTToGET(event.request);
        return fetch(get_req).then((fetchedResponse) => {
          PrintNetworkResponse(fetchedResponse);
          return fetchedResponse
        }).catch(() => {return Fallback()})
      }else{
        return Fallback()
      }
    })
  );
}

The specific URLs handled don't handle the forms causing problems.

Has anyone experienced anything similar?

Does anyone have an idea where to look?

Thanks

I think you are hitting a known WebKit (iOS/Safari) bug: https://bugs.webkit.org/show_bug.cgi?id=319985 When a Service Worker intercepts a multipart/form-data POST request and passes it blindly through fetch(event.request), Safari corrupts the request body or strips its boundaries.

Because the body arrives mangled, Django fails to parse the form fields, cannot find your csrfmiddlewaretoken, and immediately throws a 403.

To bypass the Service Worker entirely for file uploads. In your main fetch event listener, check the content type and return early so the browser's native network stack handles the multipart request directly:

self.addEventListener('fetch', event => {
    if (event.request.method === 'POST') {
        const contentType = event.request.headers.get('content-type') || '';
        
        // Bail out and let the browser handle file uploads natively
        if (contentType.includes('multipart/form-data')) {
            return; 
        }
        
        HandlePOST(event);
    }
});

Or in case u are submitting via fetch(), u can move your CSRF token into the request headers (X-CSRFToken: ) because WebKit corrupts the POST body, but leaves the headers intact allowing Django's middleware to validate the request.

Вернуться на верх