New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with CsrfViewMiddleware and "referer" same_origin checking for secure (https) subdomains #2982

Closed
troygrosfield opened this Issue May 29, 2015 · 8 comments

Comments

Projects
None yet
5 participants
@troygrosfield

troygrosfield commented May 29, 2015

This issue is laid out in detail here:

The api url is using djangorestframework. Everything works as expected when trying to POST data from http://example.com to http://api.example.com. However, when we moved to prod over https, we see the following error:

detail: "CSRF Failed: Referer checking failed - https://example.com/some/url does not match https://api.example.com/."

Both sites are secure (https) and both sites are trusted. The same_origin check happens in the CsrfViewMiddleware (below) [1] which is subclassed in djangorestframework [2]:

if not same_origin(referer, good_referer):
    reason = REASON_BAD_REFERER % (referer, good_referer)
    return self._reject(request, reason)

However, this would never seem to allow me to post across subdomains since it won't be the same_origin. It seems logical for POST requests to be made across subdomains, right? Am I just missing something? All GET requests work as expected over https.

I've already added the necessary csrftoken headers via:

and I'm using the django-cors-headers lib as well.

[1] https://github.com/django/django/blob/eef95ea96faef0b7dbbe0c8092202b74f68a899b/django/middleware/csrf.py#L159
[2] https://github.com/tomchristie/django-rest-framework/blob/010f2ee9bd8696f8332e67d73b9ad488b4423d20/rest_framework/authentication.py#L27

@troygrosfield troygrosfield changed the title from Issue with CsrfViewMiddleware and "referer" checking for trusted and secure subdomains to Issue with CsrfViewMiddleware and "referer" checking for trusted and secure (https) subdomains May 29, 2015

@troygrosfield troygrosfield changed the title from Issue with CsrfViewMiddleware and "referer" checking for trusted and secure (https) subdomains to Issue with CsrfViewMiddleware and "referer" same_origin checking for trusted and secure (https) subdomains May 29, 2015

@troygrosfield troygrosfield changed the title from Issue with CsrfViewMiddleware and "referer" same_origin checking for trusted and secure (https) subdomains to Issue with CsrfViewMiddleware and "referer" same_origin checking for secure (https) subdomains May 29, 2015

@xordoquy

This comment has been minimized.

Show comment
Hide comment
@xordoquy

xordoquy May 29, 2015

Collaborator

This doesn't look like a DRF issue since we just override the return code not the logic here.
Feel free to reopen if you manage to get a failing test case with drf component
This being said it looks like a misconfigured django-cors-headers since this library changed the origin in order for csrf to work while your doesn't

Collaborator

xordoquy commented May 29, 2015

This doesn't look like a DRF issue since we just override the return code not the logic here.
Feel free to reopen if you manage to get a failing test case with drf component
This being said it looks like a misconfigured django-cors-headers since this library changed the origin in order for csrf to work while your doesn't

@xordoquy xordoquy closed this May 29, 2015

@troygrosfield

This comment has been minimized.

Show comment
Hide comment
@troygrosfield

troygrosfield May 29, 2015

For those that encounter this issue in the future, there is a fix currently being worked on with django's core framework at in regards to this exact issue:

With discussions about it at:

troygrosfield commented May 29, 2015

For those that encounter this issue in the future, there is a fix currently being worked on with django's core framework at in regards to this exact issue:

With discussions about it at:

@troygrosfield

This comment has been minimized.

Show comment
Hide comment
@troygrosfield

troygrosfield May 29, 2015

@xordoquy, correct me if I'm wrong, but is the code that's failing validation even hitting the middleware? The place I see the failure happening is in the SessionAuthentication, which is the authentication being used, where it's doing the csrfcheck[1]:

reason = CSRFCheck().process_view(request, None, (), {})

The CSRFCheck inherits from CsrfViewMiddleware which is where the validation fails when going from https://example.com to https://api.example.com. The ordering of mysite's middleware seems to be irrelevant.

[1] https://github.com/tomchristie/django-rest-framework/blob/f7917928c080aa0b055cbfc588f61ec01f16771c/rest_framework/authentication.py#L130

troygrosfield commented May 29, 2015

@xordoquy, correct me if I'm wrong, but is the code that's failing validation even hitting the middleware? The place I see the failure happening is in the SessionAuthentication, which is the authentication being used, where it's doing the csrfcheck[1]:

reason = CSRFCheck().process_view(request, None, (), {})

The CSRFCheck inherits from CsrfViewMiddleware which is where the validation fails when going from https://example.com to https://api.example.com. The ordering of mysite's middleware seems to be irrelevant.

[1] https://github.com/tomchristie/django-rest-framework/blob/f7917928c080aa0b055cbfc588f61ec01f16771c/rest_framework/authentication.py#L130

@troygrosfield

This comment has been minimized.

Show comment
Hide comment
@troygrosfield

troygrosfield May 29, 2015

I ended up writing to custom SessionAuthentication that overrides the enforce_csrf method since it doesn't play nicely with trusted subdomains over https. Here's what ended up working for me:

from corsheaders.middleware import CorsMiddleware
from corsheaders.middleware import CorsPostCsrfMiddleware
from rest_framework.authentication import SessionAuthentication
from rest_framework import exceptions

class CustomCSRFCheck(CorsMiddleware, CsrfViewMiddleware,
                      CorsPostCsrfMiddleware):
    def _reject(self, request, reason):
        # Return the failure reason instead of an HttpResponse
        return reason

class CustomSessionAuthentication(SessionAuthentication):

    def enforce_csrf(self, request):
        """
        Enforce CSRF validation for session based authentication.
        """
        reason = CustomCSRFCheck().process_view(request, None, (), {})
        if reason:
            # CSRF failed, bail with explicit error message
            raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)

Then in your view:

class MyViewSet(ModelViewSet):
    ...
    authentication_classes = (CustomSessionAuthentication,)
    ...

Hope this helps someone else down the road.

troygrosfield commented May 29, 2015

I ended up writing to custom SessionAuthentication that overrides the enforce_csrf method since it doesn't play nicely with trusted subdomains over https. Here's what ended up working for me:

from corsheaders.middleware import CorsMiddleware
from corsheaders.middleware import CorsPostCsrfMiddleware
from rest_framework.authentication import SessionAuthentication
from rest_framework import exceptions

class CustomCSRFCheck(CorsMiddleware, CsrfViewMiddleware,
                      CorsPostCsrfMiddleware):
    def _reject(self, request, reason):
        # Return the failure reason instead of an HttpResponse
        return reason

class CustomSessionAuthentication(SessionAuthentication):

    def enforce_csrf(self, request):
        """
        Enforce CSRF validation for session based authentication.
        """
        reason = CustomCSRFCheck().process_view(request, None, (), {})
        if reason:
            # CSRF failed, bail with explicit error message
            raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)

Then in your view:

class MyViewSet(ModelViewSet):
    ...
    authentication_classes = (CustomSessionAuthentication,)
    ...

Hope this helps someone else down the road.

@a-wcyn

This comment has been minimized.

Show comment
Hide comment
@a-wcyn

a-wcyn Jul 29, 2016

Thanks @troygrosfield ! Your Custom SessionAuthentication module helped me solve a three week problem

a-wcyn commented Jul 29, 2016

Thanks @troygrosfield ! Your Custom SessionAuthentication module helped me solve a three week problem

@troygrosfield

This comment has been minimized.

Show comment
Hide comment
@troygrosfield

troygrosfield commented Jul 29, 2016

No problem!

@rlfrahm

This comment has been minimized.

Show comment
Hide comment
@rlfrahm

rlfrahm Apr 8, 2017

@troygrosfield

#2982 (comment)

This helped me out a bunch, thanks!

rlfrahm commented Apr 8, 2017

@troygrosfield

#2982 (comment)

This helped me out a bunch, thanks!

@dengshilong

This comment has been minimized.

Show comment
Hide comment
@dengshilong

dengshilong Aug 1, 2018

Contributor

Try to add CSRF_TRUSTED_ORIGINS = ['example.com'] in settings.py

Contributor

dengshilong commented Aug 1, 2018

Try to add CSRF_TRUSTED_ORIGINS = ['example.com'] in settings.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment