I read in the docs that DRF only validates CSRF tokens on authenticated requests and login views should explicitely check the CSRF token.
Problem is, how does one manually check the CSRF token?
In my settings.py
:
MIDDLEWARE = [
...
"django.middleware.csrf.CsrfViewMiddleware",
...
]
Here's my view:
from rest_framework.decorators import api_view
from django.http import JsonResponse
from django.views.decorators.csrf import get_token
# I have to manually generate the csrf token and put it in the response body, because I use react-native and that can't read the token from the 'Set-Cookie' header
@api_view(["GET"])
def user_info(request):
return JsonResponse({"csrf_token": get_token(request)})
@api_view(["POST"])
def login(request):
return JsonResponse({"foo": "bar"})
When I make a POST request in the frontend and I don't provide the CSRF token, it should fail, but I actually receive the {"foo": "bar"}
JSON.
I tried the @csrf_protect
and @requires_csrf_token
decorators, but the request is still not failing.
I also tried this, but that errors on required positional argument: 'get_response
CsrfViewMiddleware().process_view(request, None, (), {})
If I pass a function to get_response
, that function is never called:
def test_get_response(req):
breakpoint()
CsrfViewMiddleware(get_response=test_get_response).process_request(request)
I checked that the CSRF token is not passed in the headers, not the cookies, and I tried different browsers and incognito mode.
How do I get my api requests to fail if they don't include a valid CSRF token?
CodePudding user response:
You're having error in this importing from django.views.decorators.csrf import get_token
. you should import get_token
from django.middleware.csrf
.
according to django documentation and it return the CSRF token required for a POST form.
CodePudding user response:
Okay... a few rabbit holes later, I found some kind of solution.
Django uses Double Submit Cookie
for CSRF validation. This means:
- Frontend requests CSRF token from Django
- Django generates token, sends it to FE in
Set-Cookie
header - FE puts csrf in cookie, which cannot be altered by a potential attacker
- FE puts the same token in POST form and sends it to Django
- Django checks if token in POST form and cookie are the same
Problem is, React Native doesn't support cookies, so it has to send the CSRF token some other way. Problem with that is there is not way to do that securely.
I solved it by writing custom middleware for django:
- FE requests CSRF token from Django
- Django generates token, sends it to FE, AND stores hashed version in db
- FE puts it in POST form and sends it to Django
- Django hashes the token, checks if hash is in db
- Upon successful validation, Django removes token from db
- A CRON job removes any unused tokens after 24 hours
Bit cumbersome, but I only have to validate this way on web requests.