I am using Django Rest's browsable API to POST using Session Authentication, and am getting CSRF token missing, even though it was supplied. I am seeking advice on configuring my ModelViewSet subclass so that this works.
Here's my viewset:
class TreeAPI(ModelViewSet):
authentication_classes = (SessionAuthentication,)
queryset = Tree.objects.get_roots()
parser_classes = (JSONParser, FormParser, MultiPartParser)
permission_classes = (IsAdminUser,)
throttle_classes = (TreeThrottle,)
serializer_class = TreeSerializer
I am able to use the DRF Browsable API to GET this endpoint, but when I use it to POST to this endpoint, I get a 403 with the message CSRF token missing or incorrect.
When I set a breakpoint in the constructor to rest_framework.request.Request, I can see that the request passed in contains the needed csrfmiddleware token:
In Django Rest's Request class, POST is actually a property:
#property
def POST(self):
if not _hasattr(self, '_data'):
self._load_data_and_files()
if is_form_media_type(self.content_type):
# self.data is an empty QueryDict!
return self.data
return QueryDict('', encoding=self._request._encoding)
request.POST no longer contains the csrfmiddlewaretoken key; it is stripped of all keys supplied with the form:
As a result, the parameter passed to rest_framework.authentication.SessionAuthentication.enforce_csrf(request) which is then passed to django.middleware.csrf.CsrfViewMiddleware.process_view does not find the csrfmiddlewaretoken token:
if request.method == "POST":
request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
What can I check? What are the possible sources of error here?
Considerations
not interested in disabling CSRF
not interested in using token authentication
am familiar with how to use CSRF tokens and the Django docs on them
am familiar with Django REST's docs on CSRF tokens
this is the built-in Django REST browsable API; haven't modified anything in UI
EDIT 1 - Middleware
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
EDIT 2 - versions of software
- djangorestframework==3.3.3
- Django==1.9.8
EDIT 3 - possibly related issues at the git project
'request.data' empty when multipart form data POST in 3.3.x
3814
Request.data empty when multipart/form-data POSTed
3951
EDIT 4 - possibly related stack overflow posts
- Having a POST'able API and Django's CSRF Middleware
- How to make a POST simple JSON using Django REST Framework? CSRF token missing or incorrect
- How to make a Django-Rest-Framework API that takes POST data?
- Django Rest Framework, ajax POST works but PATCH throws CSRF Failed: CSRF token missing or incorrect
- http://www.django-rest-framework.org/api-guide/parsers/#formparser
This problem does not manifest in djangorestframework==3.5.4. See http://www.django-rest-framework.org/topics/release-notes/; I have a feeling this was fixed after 3.3.x.
Related
I`m using the django remote user middleware, and an own middleware mysupermiddleware:
MIDDLEWARE = [
...,
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.RemoteUserMiddleware',
'my_project.middleware.mysupermiddleware',
]
When a remote user authenticates, it is available in the view. request.user shows the username.
But in my middleware, it is not available
def mysupermiddleware(get_response):
def middleware(request):
print (request.user)
request.user returns AnonymousUser
When I print request.user in my view, it returns the remote authenticated user.
When I authenticate with ModelBackend instead RemoteUserBackend, the user is available in middleware and view.
Is this intended? How do I make the remote user available in the middleware? Django version is 3.1.1
EDIT:
Found the reason: It is related to rest framework authentication: At this stackoverflow post the user #imposeren states, that
if you are using something other than rest_framework.authentication.SessionAuthentication as an authentication_class, then request.user is set outside of middlewares (somewhere during View.dispatch)
Does somebody have an idea, on how to use user in middleware regardless?
There are tens of questions that are essentially identical to the one I'm asking. However, none of their answers seem to be working for me.
I have a React front-end where I am using axios to send requests to the back-end. Example
const request = await axios.post('${BASE_URL}/logout/')
Most of the Django Rest Framework endpoints are made with ViewSets. However, I have a few that are custom and mostly made for authentication.
path('createaccount/', views.create_account),
path('me/', views.current_user),
path('logout/', views.logout),
path('login/', views.login),
path('resetpassword', views.reset_password),
For the development of this project I've included #csrf_exempt above these views because I didn't want to deal with it at the time. Now I'm nearing deployment and it's time to figure it out.
Some answers say I need to get a CSRF Token from Django which is stored in cookies and I need to pass that in the header of each request. Some answers say all I need to do is configure axios like
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "XCSRF-TOKEN";
And it will "just work". I've tried adjusting my CSRF_COOKIE_NAME to various values to get this to work too.
Some answers even say to keep #csrf_exempt but that sounds like a very, very bad idea.
Do I actually need to generate/get a CSRF cookie? Do I include it with every request? Or is it just a configuration of axios?
To make CSRF protection work you will need CSRF cookie sent from Django to
React as a response to some request (like login or sth else). It will set cookie using
Set-Cookie on frontend side. So make sure that you have a view that does that on Django side. If not, create a view that as response generates that token.
How Django (4.04) CSRF validation work (simplified, based on middleware/csrf.py):
gets CSRF token from cookie (so frontend needs to resend it back on
another request) - it might also get it from session but in case of
React I would not use it
def _get_token(self, request):
....
try:
cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME]
except KeyError:
return None
Compares that cookie CSRF token with non-cookie token from request:
def _check_token(self, request):
# Access csrf_token via self._get_token() as rotate_token() may have
# been called by an authentication middleware during the
# process_request() phase.
try:
csrf_token = self._get_token(request)
except InvalidTokenFormat as exc:
raise RejectRequest(f"CSRF cookie {exc.reason}.")
if csrf_token is None:
# No CSRF cookie. For POST requests, we insist on a CSRF cookie,
# and in this way we can avoid all CSRF attacks, including login
# CSRF.
raise RejectRequest(REASON_NO_CSRF_COOKIE)
# Check non-cookie token for match.
request_csrf_token = ""
if request.method == "POST":
try:
request_csrf_token = request.POST.get("csrfmiddlewaretoken", "")
except UnreadablePostError:
# Handle a broken connection before we've completed reading the
# POST data. process_view shouldn't raise any exceptions, so
# we'll ignore and serve the user a 403 (assuming they're still
# listening, which they probably aren't because of the error).
pass
if request_csrf_token == "":
# Fall back to X-CSRFToken, to make things easier for AJAX, and
# possible for PUT/DELETE.
try:
request_csrf_token = request.META[settings.CSRF_HEADER_NAME]
except KeyError:
raise RejectRequest(REASON_CSRF_TOKEN_MISSING)
token_source = settings.CSRF_HEADER_NAME
else:
token_source = "POST"
try:
request_csrf_token = _sanitize_token(request_csrf_token)
except InvalidTokenFormat as exc:
reason = self._bad_token_message(exc.reason, token_source)
raise RejectRequest(reason)
if not _does_token_match(request_csrf_token, csrf_token):
reason = self._bad_token_message("incorrect", token_source)
raise RejectRequest(reason)
As you can see you either need to include csrfmiddlewaretoken in POST request or include it in header with key: settings.CSRF_HEADER_NAME and value read from cookies on front-end side.
So for example you set withCredentials: true (to include initial cookie), read that initial CSRF cookie in React and add to header in axios request at specific key.
When in question, I would just debug request setting up breakpoints in this code of Django in middleware/csrf.py and you can trace what is missing and why CSRF validation fails.
I've got this problem once, I was using token authentication. That's how I solved it. But not sure If it is the best idea. I only used csrf_exempt for this view and all others views are viewsets.
#csrf_exempt
def get_current_user(request, *args, **kwargs):
if request.method == 'GET':
user = request.user
serializer = UserDataSerializer(user)
return JsonResponse(serializer.data, safe=False)
My middleware in settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
# 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.locale.LocaleMiddleware',
'oauth2_provider.middleware.OAuth2TokenMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'auditlog.middleware.AuditlogMiddleware',
]
Some Views in my Django 11.1 application get requested by both GUI Templates (user must login for access), and by anonymous python client apps executed as a CLI program (no login).
I needed to enable CSFR protection for all Views, but this protection requires a session id to work (without as session, CSFR attack wouldn't work anyway).
I came up with the solutions shown bellow, but would like to know if there is a better way of doing it.
Solution for GUI templates: This is rather simple and well documented in Django docs. Just include {% csrf_token %} between POST tags.
Solution for Python clients (anonymous): This solution is not clearly documented in the docs (or perhaps it is, but I didn't find an easy step by step instruction). So this is what I did:
In globals.py:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
Create a Django View that returns a CSFR token and a session ID for anonymous clients:
from django.middleware.csrf import get_token
...
csfr_token = get_token(request)
...
if not request.session.session_key:
request.session.create()
....
return JsonResponse({'token': csfr_token, 'sessionid' : request.session.session_key})
The anonymous client, first requests the token and session ID with a GET:
...
myrequest = urllib.request.Request(url_to_the_django_view, method='GET')
myresponse = urllib.request.urlopen(myrequest )
myresponse _read = eval(myresponse.read())
return myresponse _read['token'], myresponse _read['sessionid']
Lastly, send a post request with CSFR information in the POST form and in HTTP header:
...
req.add_header('X-CSRFToken', token)
req.add_header('Cookie', 'csrftoken=' + token + ';' + 'sessionid=' + sessionid)
...
urllib.request.urlopen(req)
...
I've been spending hours trying to find a way to have Basic HTTP Authentication for one of my views. These are several of the solutions I've tried but have had no success. The request is still be processed even with no authentication. I'm using version 1.4.3 of Django. Here is my Django view:
#csrf_exempt
def facebook(request):
if request.user.is_authenticated():
fb_value= ast.literal_eval(request.body)
queryset = Poster.objects.all().filter(fb_id__in = fb_value.values())
data = serializers.serialize('json', queryset, fields = ('picture','fb_id',))
return HttpResponse(data, 'application/javascript')
else:
return HttpResponse("This user is not authenticated")
I sent in the request without authentication, and it still returned results. This is not suppose to happen.
Another solution I tried was from a Django Snippet I found called, view by view basic authentication decorator
I made a httpauth.py and copied the code over from the snippet:
from mydjangoapp.httpauth import *
#csrf_exempt
#logged_in_or_basicauth()
def facebook(request):
fb_value= ast.literal_eval(request.body)
queryset = Poster.objects.all().filter(fb_id__in = fb_value.values())
data = serializers.serialize('json', queryset, fields = ('picture','fb_id',))
return HttpResponse(data, 'application/javascript')
I sent the request without authentication, and it still returned results. After exhausting all options, I turned to Django's very own #login_required decorator:
from django.contrib.auth.decorators import login_required
#csrf_exempt
#login_required
def facebook(request):
fb_value= ast.literal_eval(request.body)
queryset = Poster.objects.all().filter(fb_id__in = fb_value.values())
data = serializers.serialize('json', queryset, fields = ('picture','fb_id',))
return HttpResponse(data, 'application/javascript')
Here is more information about my settings.py:
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
After trying all of these options, I don't know what to do. Is there anything I am missing here?!
This really isn't much of an answer. I'm sorry that I have to post it here, but the system has cut me off from the comment section.
I don't see any issues with your example with using the #login_required decorator. This is typically how I do it on my Django sites. This leads me to believe that you have 1 of 2 things going on here:
You have a configuration issue in your settings file
During initial testing, you have actually authenticated and created a session.
Again, I don't think your problem is with your code. Please post what you finally determine is the issue so that I (and others) may learn from it.
How do you handle csrf credentials sent to django as url parameters?
I ask because that is, evidently, the only way to submit a file upload via a form in an iFrame.
Most online examples show to pass csrf credentials as headers,
xhr.setRequestHeader("X-CSRFToken", csrfToken );
but this is not an option for iFrame transport in ie/opera.
I can use csrf_exempt, but this leaves my site vulnerable.
You could create some middleware that takes csrf_token from the GET params and places it on the request before CsrfViewMiddleware attempts to validate
class CsrfGetParamMiddleware(object):
def process_request(self, request):
request.META['HTTP_X_CSRFTOKEN'] = request.GET.get('csrf_token')
return None
Place this middleware above the CsrfViewMiddleware
MIDDLEWARE_CLASSES = (
'CsrfGetParamMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
)
This save you from validating it yourself or subclassing CsrfViewMiddleware