passing CSRF credentials as url parameters? - django

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

Related

csrf_exempt set but CSRF Failed: Referer checking failed - no Referer

I have a backend API, it's in django and deployed on Google Endpoint.
I have a post request that insert data to my DB.
I created a script to use this endpoint but I got this error:
{"detail":"CSRF Failed: Referer checking failed - no Referer."}
Regarding over posts I added the crsf_exempt decorator to my class but it did not change.
I try to add the decorator two ways:
class AddUser(APIView):
""" Create user and company from csv """
#method_decorator(csrf_exempt)
def post(self, request):
#method_decorator(csrf_exempt, name='dispatch')
class AddUser(APIView):
""" Create user and company from csv """
def post(self, request):
But both failed.
This is how I contact my endpoint:
resp = requests.request(
method, url,
headers={'Authorization': 'Bearer {}'.format(
open_id_connect_token)}, **kwargs)
Any ideas ?
Thanks
EDIT
So I tried to add authentication classes to my views but it appears to be a bad idea. This is being real trouble for me.
I tried to get the csrftoken doing like this:
client = requests.session()
# Retrieve the CSRF token first
client.get(url) # sets cookie
print(client.cookies)
if 'csrftoken' in client.cookies:
# Django 1.6 and up
csrftoken = client.cookies['csrftoken']
else:
# older versions
csrftoken = client.cookies
Thing is, I am using IAP to protect my API and I do not have any csrftoken cookie but I do have a something looking like this:
<RequestsCookieJar[<Cookie GCP_IAP_XSRF_NONCE_Q0sNuY-M83380ypJogZscg=1
for ...
How can I use this to make post request to my API ?
So this happened to me because I did not set any authentication_classes to my generic view.
When this option is not set Django automatically use the SessionBackend, which need the csrf token.
I fixed it by adding this to my view: authentication_classes = [ModelBackend, GoogleOAuth2]
#Kimor - Can you try doing this in your urls.py
from django.views.decorators.csrf import csrf_exempt
url('^test/$', csrf_exempt(views.TestView.as_view())),
The get and post methods defined on the APIView class just tell DRF how the actual view should behave, but the view method that the Django router expects is not actually instantiated until you call TestView.as_view().
source
Django REST Framework CSRF Failed: CSRF cookie not set
So after working on this project for a while this is what I learned regarding the CSRF using Django.
First of all, if you are using django templates, or in any cases where your back-end and front-end are running behind the same server the most common practice is to use session for authentication.
This is activated by default by DRF.
This means that in your DRF configuration if you do not explicitly set the DEFAULT_AUTHENTICATION_CLASSES option default authentication will be set to Session + BasicAuth.
In this configuration you'll need to manage the CSRF token as described in the documentation (https://docs.djangoproject.com/en/4.0/ref/csrf/).
If your back-end and front-end are separated as in my case, using CSRF is not the only solution or even the recommended one.
As in my case I use JWT behind IAP (Identity Aware Proxy, provided by google). I had to write my own authentication classes and then use it in my DRF conf:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'main.authentication_backend.custom_auth.IAPAuthentication'],
...
}
Here is explain how to write your own authentication class: https://www.django-rest-framework.org/api-guide/authentication/#custom-authentication.

Django (DRF) & React - Forbidden (CSRF cookie not set)

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',
]

Enable Django View CSFR protection for anonymous Python clients using API requests (rather than templates)

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)
...

Django DRF CSRF token missing - DRF deleted request.POST data?

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.

django does not send csrf token again after browser cookies has been cleared

In normal situation, django will send csrf token via cookie which can be used by ajax post method later. However when I clear cookies in the browser(Chrome or Firefox), the csrf token is not sent to browser anymore, the session id is still sending but no csrf token. Does anyone know what's going wrong?
I solved this issue by adding {% csrf_token %} to my template and the SET-COOKIE header appears along with that page request. it turns out that you have to put the {%csrf-token%} in the template in order to make the server send the token via SET-COOKIE header
Look at django/middleware/csrf.py in which CsrfViewMiddleware class is declared. As you can see in def process_response(self, request, response) there are three conditions that prevent cookie setup:
def process_response(self, request, response):
if getattr(response, 'csrf_processing_done', False):
return response
# If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was
# never called, probaby because a request middleware returned a response
# (for example, contrib.auth redirecting to a login page).
if request.META.get("CSRF_COOKIE") is None:
return response
if not request.META.get("CSRF_COOKIE_USED", False):
return response
# Set the CSRF cookie even if it's already set, so we renew
# the expiry timer.
response.set_cookie(settings.CSRF_COOKIE_NAME,
request.META["CSRF_COOKIE"],
max_age = 60 * 60 * 24 * 7 * 52,
domain=settings.CSRF_COOKIE_DOMAIN,
path=settings.CSRF_COOKIE_PATH,
secure=settings.CSRF_COOKIE_SECURE
)
# Content varies with the CSRF cookie, so set the Vary header.
patch_vary_headers(response, ('Cookie',))
response.csrf_processing_done = True
return response
Check which is applied for you.
I had the same problem. After debugging against django source, the reason is:
If your view is not rendering a template containing the csrf_token
template tag, Django might not set the CSRF token cookie.
Two solutions:
Add {% csrf_token %} in your template
Use #ensure_csrf_cookie decorator for your view
For detail your can refer django doc.
In my case, the problem was VSCode debugger.
I turned on server via VSCode debug mode, then open new incognito window (obviously there were no cookies), and django stopped setting missing cookie.
When I started server as normal, the problem has gone.
In most cases issue caused by second check mentioned in a previous answer
if not request.META.get("CSRF_COOKIE_USED", False):
return response
This can be solved by using #ensure_csrf_cookie decorator for the view. If used - check is passed and cookie is set/renewed each time view is rendered.
See also related topic: Using ajax request in Django without form element