Django CSRF Token present but still getting 403 Forbidden Error - django

I am trying to set up a Django API that receives POST requests with some JSON data and basically sends emails to a list of recipients. The logic is rather simple:
First I have the view for when I create a blog post. In the template, I include the csrf_token as specified on the Django Documentation. When I hit the submit button, behind the scene the create-post view, in addition to creating the post, makes a request (I am using the requests module) to the API which is charged with sending the emails. This is the piece of logic the sends the request to the API:
data = {
"title": new_post.title,
"summary": new_post.summary,
"link": var["BASE_URL"] + f"blog/post/{new_post.slug}"
}
csrf_token = get_token(request)
# print(csrf_token)
headers = {"X-CSRFToken": csrf_token}
requests.post(var["BASE_URL"] + "_api/send-notification/", json=data, headers=headers)
As you can see I am adding the X-CSRFToken to the headers which I generate through the get_token() method, as per the Django docs. However, the response in the API is a 403 Forbidden status CSRF Token not set.
I have checked the headers in the request and the token is indeed present. In addition, I have been providing other routes to the API and I have been using it for AJAX calls which again is very simple just follow the Django docs and they work perfectly well.
The problem seems to arise when I make the call from within the view, AJAX calls are handle by Javascript static files, and as I said they work fine.
I have thought that Django didn't allow the use of 2 CSRF tokens on the same page (one for the submit form and the other in the view by get_token()), but that's not the problem.
This is typically the error I get:
>>> Forbidden (CSRF cookie not set.): /_api/send-notification/
>>> "POST /_api/send-notification/ HTTP/1.1" 403 2864
I have read several similar questions on SO but they mostly involved using the csrf_exempt decorator, which in my opinion is not really a solution. It just gets rid of the CRSF token usefulness altogether.
Does anyone have any idea what might be causing this problem?
Thanks

Error tries to tell you that you need to add token into cookie storage like that:
cookies = {'csrftoken': csrf_token}
requests.post(var["BASE_URL"] + "_api/send-notification/", json=data, headers=headers, cookies=cookies)

Related

Add CSRF to locust post request to prevent django error - Forbidden (CSRF cookie not set.)

How to add CSRF to the URL to test django from locust to prevent the error Forbidden (CSRF cookie not set.)?
Here is what I have tried:
#task
def some_task(self):
response = self.client.get("api/test/")
csrftoken = response.cookies['csrftoken']
self.client.post(
"api/test/",
{"csrfmiddlewaretoken": csrftoken},
headers={"X-CSRFToken": csrftoken},
cookies={"csrftoken": csrftoken})
The error I get is
KeyError: "name='csrftoken', domain=None, path=None"
Try to debug your response.cookies because you could have the "csrftoken" inside a class, which was my case.
I was getting something like this when I printed on the console:
print(response.cookies)
<RequestsCookieJar[Cookie(version=0, name='csrftoken', value='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
which it will result in a KeyError: "name='csrftoken' if I try to access it via response.cookies['csrftoken']
What you need to do to get this value is response.cookies.get_dict()["csrftoken"]. This way you will be able to get the artribute value from inside the RequestsCookieJar class.
You can have this problem when the CSRF_COOKIE_SECURE setting is True.
I believe browsers are smart enough to ignore the secure setting when making requests to 127.0.0.1 so you don't notice the issue during normal development, but Locust respects the cookie's secure setting and will not send the CSRF cookie to http://127.0.0.1:8000, for example.
I had the exact same error and using CSRF_COOKIE_SECURE=False fixed it. You may also want SESSION_COOKIE_SECURE=False.

#JWT_Required decorator Exception Handling

I am using an auth API using JWT and it works great.
This API is being used to authorize users for my web app. For this to work, I store JWT access_tokens as cookie manually with Flask.
I secure my resource with #JWT_required decorator and if I try to access a secure resource with a valid token everything works fine.
However, if the access token is missing or invalid/expired I get a JSON saying:
{
"message": "Missing cookie \"access_token_cookie\""
}
This is obvious the right message but rather then showing a JSON I want to redirect to the appropriate statuscode error page that is provided by Flask - in this case 401.
I have tried adding error handling for Flask and JWT Manager
Custom decorator, although I have played only poorly with this as I believe there has to be solution within FLASK-JWT-extended
#app.route('/dashbord')
#jwt_required
def dashbord():
return render_template('dashbord.html', title='Home')
My goal is to redirect to appropriate error page 404, 403, 401 if anything is wrong with the access token.
THE SOLUTION:
#jwt.unauthorized_loader
def my_invalid_token_callback(expired_token):
return render_template('401.html', title='Home')
Here's the solution Benjo posted at the bottom of his question:
#jwt.unauthorized_loader
def my_invalid_token_callback(expired_token):
return render_template('401.html', title='Home')
Here is the documentation for changing the results for invalid tokens: https://flask-jwt-extended.readthedocs.io/en/stable/changing_default_behavior.html#changing-callback-functions

Using Django REST Framework with sessions-based CSRF

I am using Django + Django REST Framework in an environment where I cannot use cookie-based CSRF tokens; therefore I must run with CSRF_USE_SESSIONS = True.
The DRF web UI, however, depends on this cookie for all interactions. It appears this is set by reading the csrftoken cookie and settings the X-CSRFToken header on the subsequent request, which is then consumed by django.middleware.csrf.CsrfViewMiddleware.process_view() if the hidden field is not included in the request body. This is set in this code from rest_framework.templates.rest_framework.base.html:
<script>
window.drf = {
csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}",
csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}"
};
</script>
DRF forms that do not use POST do contain the CSRF token in the form body, so not having the cookie means the web interface does not have access to the CSRF token at all, causing all PUT, PATCH, and DELETE requests to fail with a 403 response.
I believe this is a bug in DRF, but it is possible this is intended behavior. Can someone explain how DRF is intended to be used with CSRF_USE_SESSIONS = True?
This was fixed in https://github.com/encode/django-rest-framework/pull/6207 and released as part of DRF 3.9.2. More complete context can be read at https://github.com/encode/django-rest-framework/issues/6206.

Django csrf Error: CSRF token missing or incorrect [duplicate]

Using django.contrib.auth.views.login() to process user logins I'm seeing 403 responses in a production environment. A second attempt to login succeeds after an initial 403 (when that response occurs).
I've begun to log all 403 login failures, capturing the POST payload and cookie values which shows that csrfmiddlewaretoken (the hidden form field value) and csrftoken (cookie value) don't match. It's intermittent and happens to many users.
The following decorators are all applied to the login function being used to proxy the django.contrib.auth.views.login() function: #ensure_csrf_cookie, #sensitive_post_parameters, #csrf_protect, #never_cache
What might be the causes of this problem?
The CSRF token is rotated after login.
If you open the login page in one tab, login using a second tab, then you'll get a CSRF error if you submit the form on the original tab.

How to make a POST simple JSON using Django REST Framework? CSRF token missing or incorrect

Would appreciate someone showing me how to make a simple POST request using JSON with Django REST framework. I do not see any examples of this in the tutorial anywhere?
Here is my Role model object that I'd like to POST. This will be a brand new Role that I'd like to add to the database but I'm getting a 500 error.
{
"name": "Manager",
"description": "someone who manages"
}
Here is my curl request at a bash terminal prompt:
curl -X POST -H "Content-Type: application/json" -d '[
{
"name": "Manager",
"description": "someone who manages"
}]'
http://localhost:8000/lakesShoreProperties/role
The URL
http://localhost:8000/lakesShoreProperties/roles
DOES work with a GET request, and I can pull down all the roles in the database, but I can not seem to create any new Roles. I have no permissions set. I'm using a standard view in views.py
class RoleDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Role.objects.all()
serializer_class = RoleSerializer
format = None
class RoleList(generics.ListCreateAPIView):
queryset = Role.objects.all()
serializer_class = RoleSerializer
format = None
And in my urls.py for this app, the relevant url - view mappings are correct:
url(r'^roles/$', views.RoleList.as_view()),
url(r'^role/(?P<pk>[0-9]+)/$', views.RoleDetail.as_view()),
Error message is:
{
"detail": "CSRF Failed: CSRF token missing or incorrect."
}
What is going on here and what is the fix for this? Is localhost a cross site request? I have added #csrf_exempt to RoleDetail and RoleList but it doesn't seem to change anything. Can this decorator even be added to a class, or does it have to be added to a method?
Adding the #csrf_exempt decorate, my error becomes:
Request Method: POST
Request URL: http://127.0.0.1:8000/lakeshoreProperties/roles/
Django Version: 1.5.1
Exception Type: AttributeError
Exception Value:
'function' object has no attribute 'as_view'
Then I disabled CSRF throughtout the entire app, and I now get this message:
{"non_field_errors": ["Invalid data"]} when my JSON object I know is valid json. It's a non-field error, but I'm stuck right here.
Well, it turns out that my json was not valid?
{
"name": "admin",
"description": "someone who administrates"
}
vs
[
{
"name": "admin",
"description": "someone who administrates"
}
]
Having the enclosing brackets [], causes the POST request to fail. But using the jsonlint.com validator, both of my json objects validate.
Update: The issue was with sending the POST with PostMan, not in the backend. See https://stackoverflow.com/a/17508420/203312
CSRF is exempted by default in Django REST Framework. Therefore, curl POST request works fine. POSTMAN request call returned CSRF incorrect because POSTMAN included csrf token if it is found in Cookies. You can solve this by cleaning up Cookies.
It's from your REST Framework settings. in your settings.py file, your REST_FRAMEWORK should have the following.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
}
This will set your REST Framework to use token authentication instead of csrf authentication. And by setting the permission to AllowAny, you can authenticate only where you want to.
You probably need to send along the CSRF token with your request. Check out https://docs.djangoproject.com/en/1.7/ref/contrib/csrf/#csrf-ajax
Update: Because you've already tried exempting CSRF, maybe this could help (depending on which version of Django you're using): https://stackoverflow.com/a/14379073/977931
OK, well now of course I take back what I said. CSRF does work as intended.
I was making a POST request using a chrome plugin called POSTMAN.
My POST request fails with CSRF enabled.
But a curl POST request using
curl -X POST -H "Content-Type: application/json" -d '
{
"name": "Manager",
"description": "someone who manages"
}' http://127.0.0.1:8000/lakeshoreProperties/roles/
works fine...
I had to take off the braces, i.e., [], and make sure there is a slash after the 's' in roles, i.e., roles/, and csrf enabled did not throw any errors.
I'm not sure what the difference between calling using POSTMAN is vs using curl, but POSTMAN is run in the web browser which is the biggest difference. That said, I disabled csrf for the entire class RoleList but one identical request works with Curl, but fails with POSTMAN.
To give an update on current status, and sum up a few answers:
AJAX requests that are made within the same context as the API they are interacting with will typically use SessionAuthentication. This ensures that once a user has logged in, any AJAX requests made can be authenticated using the same session-based authentication that is used for the rest of the website.
AJAX requests that are made on a different site from the API they are communicating with will typically need to use a non-session-based authentication scheme, such as TokenAuthentication.
Therefore, answers recommending to replace SessionAuthentication with TokenAuthentication may solve the issue, but are not necessarily totally correct.
To guard against these type of attacks, you need to do two things:
Ensure that the 'safe' HTTP operations, such as GET, HEAD and OPTIONS cannot be used to alter any server-side state.
Ensure that any 'unsafe' HTTP operations, such as POST, PUT, PATCH and DELETE, always require a valid CSRF token.
If you're using SessionAuthentication you'll need to include valid CSRF tokens for any POST, PUT, PATCH or DELETE operations.
In order to make AJAX requests, you need to include CSRF token in the HTTP header, as described in the Django documentation.
Therefore, it is important that csrf is included in header, as for instance this answer suggests.
Reference: Working with AJAX, CSRF & CORS, Django REST framework documentation.
As you said your URL was
http://localhost:8000/lakesShoreProperties/roles
Postman has some issues with localhost.
Sending the POST to 127.0.0.1:8000/your-api/endpoint instead did the trick for me.
the old Postman is having a problem with csrf tokens because it does not working with cookies.
I suggest for you to switch to the new version of postman, it works with cookies and you will not face this problem again.
if you have set AllowAny permission and you facing with csrf issue
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny'
]
}
then placing following in the settings.py will resolve the issue
REST_SESSION_LOGIN = False