Ktor client - CSRF post request - django

I am doing a project where I am using django for server and ktor client for jetpack compose application to make request.However the CSRF protection reject my login request(An unsafe post request).
As django has a built-in CSRF protection middleware, when I am testing the login post request with localhost, the server return Forbidden (CSRF cookie not set.): /user/login/ to the client and the login function cannot work. I tried to search for some documents and solutions to disable CSRF check (#csrf_exempt) but they are not working for me.I have added the CSRF_TRUSTED_ORIGINS in setting.py as the following(To be honest I don't know if these works or not):
CSRF_TRUSTED_ORIGINS = [
'http://localhost',
'http://*.127.0.0.1:*',
'http://10.0.2.2',
'http://127.0.0.1',
'https://127.0.0.1',
'https://127.0.0.1:*',
'https://127.0.0.1:',
]
I have also tried to disable the middleware but not work.
Is there any way that I can use ktor client to satisfy the CSRF thing from django?? Or what else should I do if that is not possible.
Thank you for any answer.

In django views.py I add a function like this:
#csrf_exempt
def get_csrf(request):
token = csrf.get_token(request)
response = JsonResponse({'detail': 'CSRF cookie set','CSRFToken': token})
return response
In the ktor client , I add something like this:
#Serializable
data class Csrf_response(
val detail:String,
val CSRFToken : String,
)
private suspend fun getCsrf():Csrf_response{
return httpClient.post("${hostUrl}/user/get_csrf/").body()
}
suspend fun postLogin(loginCredential: LoginCredential): UserInfo {
val token = getCsrf()
// Log.d("token: ",token.CSRFToken)
return httpClient.post("${hostUrl}/user/login/") {
setBody(loginCredential)
cookie(name = "X-CSRFToken", value = token.CSRFToken)
header("X-CSRFToken",token.CSRFToken)
}.body()
}
The function get_csrf in views.py generate a random csrf token.
The getCsrf() function in ktor client get the generated csrf token.
Then in your login(or whatever request needs csrf) add the header and cookie and set the value of the token.
This might not be the best solution but it implements the csrf validation in ktor(I think).

Related

Django REST social login with dj_rest_auth does not authenticate the user

I am building an application. The client is built with Next.js and the backend with Django and Django REST framework.
In this application, I would like to have social login.
So far, my situation is this.
I have set up the OAuth on the Google dashboard
On the client, I am using next-auth - The client is successfully calling Google and getting an access token from there.
On the client, the callback that runs after getting the access token from Google makes a call my Django API.
I have set up the backend with dj_rest_auth - My settings are almost identical to the ones described here.
Once the client callback runs and calls my Django API with the access token from Google, I successfully get on the client an access token and a refresh token.
If it is a new user loggin in the first time, a new user is created in Djangos DB
const response = await fetch(`${djangoAPIurl}/api/social/login/google/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
access_token: accessToken,
id_token: idToken
})
});
const data = await response.json();
const { access_token, refresh_token } = data;
Both access_token and refresh_token are defined and appear to be valid tokens.
So far, everything happens as expected. My issue appears after this point.
In my api, I have another view defined.
#api_view(['GET'])
#authentication_classes([SessionAuthentication, BasicAuthentication, TokenAuthentication])
#permission_classes([IsAuthenticated])
def test_view(request):
current_user = request.user
print('current_user.auth: ', current_user.is_authenticated)
response = JsonResponse({"received": True})
return response
From my client, I am attempting to call this view in the following way.
const response = await fetch(`${djangoAPIurl}/api/test/test_view/`, {
headers: new Headers({
Authorization: `Bearer ${session.accessToken}`
})
});
The header is constructed correctly, with session.accessToken being the value I got from the api/social/login/google/ call and the request is routed correctly, however, it fails with Forbidden 403 because the user is not authenticated. I have removed the authentication and permission decrators and the request ends up being processed by the view, and there, upon inspection of the user, it is an Anonymous user. I have also tried changing Bearer to Token, to no avail.
Do you have any advice what I might be doing wrong or missing? Have I completely missunderstood how to use the token I get back from api/social/login/google/? All advice is much appreicated!
I think this is because your secret for hashing JWTS on the client side and server side is not same. Next-Auth automatically creates a secret key for hashing jwt's and dj_rest_auth does the same, unless you explicitly tell them both to use the same secret for hashing jwts. I'm a bit late to answer this, but Hope this will help future people😁😁.

Django CSRF Token present but still getting 403 Forbidden Error

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)

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: why CSRF_COOKIE in request.META

I used requests to send request.
import requests
def print(req):
print('{}\n{}\n{}\n\n{}'.format(
'-----------START-----------',
req.method + ' ' + req.url,
'\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
req.body,
))
print "----------END------------"
try:
req = requests.Request('GET',
'https://myip/myproject/upload/token',
headers={'Authorization': 'Token 401f7ac837a',
})
prepared = req.prepare()
print(prepared)
except Exception as e:
print "Exception:", e
Output:
-----------START-----------
GET https://myip/myproject/upload/token
Authorization: Token 401f7ac837a
None
----------END------------
But after I printed the request.META, there is
META:{u'CSRF_COOKIE': u'YGzoMaNEQJz1Kg8yXAwjJt6yNuT9L'
What set the CSRF_COOKIE?
Any comments welcomed. Thanks
UPDATE
(1)
From the doc, it said This cookie is set by CsrfViewMiddleware, which means the CSRF cookie was set in back-end and set to front-end in the response (CSRF cookie: server -> browser). Why it also said For all incoming requests that are not using HTTP GET, HEAD, OPTIONS or TRACE, a CSRF cookie must be present? And why it appears in my request.META? (CSRF cookie: browser -> server ???)
(2)
It said **A hidden form field with the name ‘csrfmiddlewaretoken’ present in all outgoing POST forms. The value of this field is the value of the CSRF cookie.
This part is done by the template tag.
**
When and How the template tag do it?
This is a standard cookie Django applications spin up for each new user to prevent Cross Site Forgery.
A CSRF cookie that is set to a random value (a session independent
nonce, as it is called), which other sites will not have access to.
This cookie is set by CsrfViewMiddleware. It is meant to be permanent,
but since there is no way to set a cookie that never expires, it is
sent with every response that has called
django.middleware.csrf.get_token() (the function used internally to
retrieve the CSRF token).
For security reasons, the value of the CSRF cookie is changed each
time a user logs in.
for more reading
https://docs.djangoproject.com/en/1.9/ref/csrf/#how-it-works

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