Django session not available on two seperate requests - python

Description:
In the django session docs it says:
You can read it and write to request.session at any point in your view.
But I can't access the session when making a second request to the same view:
views.py
class Login(APIView):
def post(self, request):
print("before: ", request.session.get("user")
request.session["user"] = "admin"
print(request.session.get("user")) #outputs 'admin'
return Response()
Expected Output:
After the second request (made with jquery $.post) it should output:
"admin"
Output:
Instead it outputs:
None
How can I make sessions available between independend requests?

As mentioned by #AbdulAzizBarkat in the comments, the problem was that the session credentials were not sent to the backend. The way the sessions work in a cross-domain scenario is:
User is verified in backend
Session is sent to the frontend and stored in the browser
The session credentials have to get sent to the backend on every request
You cannot, however, read this session cookies, like mentioned here:
The browser cannot give access to 3rd party cookies like those received from ajax requests for security reasons, however it takes care of those automatically for you!
The provided solution using ajax and setting xhrFields: { withCredentials: true } did not work for me.
Answer:
Instead of an ajax request, I used fetch requests.
It is important to set credentials: "include" since otherwise cookies won't be sent cross-origin. A request looks like this:
fetch(`${API}/login`, {
credentials: "include",
method: "POST",
body: data,
}).then(...).catch(...);

Related

Enabling CSRF for Django

I have the following python code in my Django views.py, the code takes in a JSON body and send the extracted DATA to another API endpoint, I have simplified the code here.
How do I enable csrf such that it will send the token back to the caller for this method? I am calling this from postman.
#csrf_protect
def validate_booking(request):
if request.method != "POST":
return HttpResponseServerError("Invalid HTTP method")
body = json.loads(request.body)
booking_details = body["booking_details"]
DATA = {
"name": booking_details["name"],
"nric": booking_details["nric"],
"booking_id": booking_details["booking_id"]
}
return HttpResponse(status="200")
This site directs to put this piece of code in my method. But what is "a_template.html"?
https://docs.djangoproject.com/en/4.1/ref/csrf/
#csrf_protect
def my_view(request):
c = {}
# ...
return render(request, "a_template.html", c)
This isn't an easy thing to do as CSRF is 2 steps thing
There is a value that is passed to the client and it is saved to the session on the server.
When a POST request is received, the client shall send this as csrfmiddlewaretoken in the body and the server will check the value against the stored one in the server's session.
So this isn't feasible to be done in APIs as you require session Management which is not of REST API implementations.
Thanks for your reply. I managed to find a solution by doing the following:
Create a new GET method that will generate the session CSRF token using python
Instead of using render which expects a HTML template file, I used JsonResponse(data) to return in JSON format directly
In my postman app which I am making the POST request with the X-CSRFToken in the header, I will first make a GET request to the new method I created in step 1 to retrieve the token and store it as an environment variable
The following is the GET method sample:
from django.http import JsonResponse
def get_csrf_token(request):
csrf_token = csrf(request)['csrf_token']
data = {'csrf_token': csrf_token}
return JsonResponse(data)

Authenticate an API call correctly with requests and sessions

I want to call my own API in a custom view I wrote. Normally I use JWT authentication with my API calls. In this specific view though, I'd like to use a different authentication.
I want to enable logged in users to make a successful get call (without a token). Not logged in users should not be able to make that call. I tried this with Basic Authentication and Session Authentication but don't really get it tow work.
Here is my view that makes the API call:
def visualize_buildings(request, id):
passed_id = id
endpoint = 'linktomyendpoint' + str(passed_id)
response = requests.get(endpoint)
building_group_data = response.json()
# print(building_group_data)
if 'buildings' in building_group_data:
building_data = building_group_data['buildings']
context = {'building' : building_data}
return render(request, 'building_group_visualize_api.html', context)
else:
return HttpResponseNotFound("Ups. We are sorry but no Building Group was found with that id")
Here my API view:
class BuildingGroupRetrieveAPIView(RetrieveAPIView):
authentication_classes = [JSONWebTokenAuthentication,
SessionAuthentication, BasicAuthentication]
serializer_class = BuildingGroupSerializer
queryset = BuildingGroup.objects.all()
The view works with if I send a token in the headers. But how can I use Session Authentication with that? I tried getting username and password from the request and then pass it to the API call. But that doesn't work because I can't decode the password from the request (which makes sense).
So I tried to follow this: https://2.python-requests.org/en/master/user/advanced/ but I still can't authenticate my request.
Can anyone point me into the right direction? Help is very much appreciated! Thanks in advance!
Session ids are saved as a cookie on the user's device and they will be sent to the server as a header name Cookie. So if you want to use cookies instead of the JWT token then you should send your request with the session id as a cookie header.
This is the header that lets Django know your session-id when you visit the site directly:
Cookie: csrftoken=some-csrf-token; sessionid=your-session-id
Now to make your request contain something like that:
cookies = {'sessionid': 'your-session-id'}
response = requests.get(endpoint, cookies=cookies)
Note that Django might still through an error for csrf token based on your settings.
You can find your session-id on your browser. If you don't know where and how to access them, just google it. it's different based on the browser you use.

Does SessionAuthentication work in Tastypie for HTTP POST?

I am able to do GET to work with SessionAuthentication and Tastypie without setting any headers except for content-type to application/json. HTTP POST however just fails even though the Cookie in the Header has the session id. It fails with a 401 AuthorizationHeader but it has nothing to do with Authorization. Changing SessionAuthentication to BasicAuthentication and passing username/password works too.
Has anyone ever got SessionAuthentication to work with POST with Tastypie?
Yes I have gotten it to work. All you need to do is to pass the csfr token:
SessionAuthentication
This authentication scheme uses the built-in
Django sessions to check if a user is logged. This is typically useful
when used by Javascript on the same site as the API is hosted on.
It requires that the user has logged in & has an active session. They
also must have a valid CSRF token.
This is how you do that in jQuery:
// sending a csrftoken with every ajax request
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
crossDomain: false, // obviates need for sameOrigin test
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
}
}
});
$.ajax({
type: "POST",
// ...
Notice the part that says $.cookie('csrftoken'). It gets the csrf token from a cookie that Django sets.
Update:
I had some problems with Django not setting the cookie on Firefox and Opera. Putting the template tag {% csrf_token %} in your template solves this. The right solution would probably be to use the decorator ensure_csrf_cookie().
Here are some additions to Dan's answer. Please, correct me if something is wrong, I am still a bit confused about it myself.
Before we continue, read about CSRF protection in Django. Read it carefully. You need to put the token from the cookie into the header X-CSRFToken. This will not work if the cookie is Httponly, that is, if you have set CSRF-COOKIE-HTTPONLY = True in settings.py. Then you have to embed the cookie in the document which, of course, creates further vulnerabilities and reduces protection the gained by using Httponly.
As far as I can tell, if the cookie is not Httponly, jQuery sets X-CSRFToken automatically. Correct me if I am wrong, but I've just spent several hours playing with it, and this is what I am consistently getting. This makes me wonder, what is the point of the advice in Django documentation? Is it a new feature in jQuery?
Further discussion:
Tastypie disables CSRF protection except with Session Authentication, where it has custom code in authentication.py. You have to pass both the cookie csrftoken cookie and the header X-CSRFToken for the authentication to work. (This is Tastypie's requirement.) Assuming same domain, the browser will pass the cookies. JQuery will pass the header for you unless the csrftoken cookie is Httponly. Conversely, if the cookie is Httponly, I was unable to even manually set the header in $.ajaxSetup{beforeSend.... It appears that jQuery automatically sets X-CSRFToken to null if the csrftoken cookie is Httponly. At least I was able to set the header X-CS_RFToken to what I wanted, so I know I passed the value correctly. I am using jQuery 1.10.
If you are using curl for testing, you have to pass two cookies (sessionid and csrftoken), set the headers X-CSRFToken and, if the protocol is HTTPS, also set the Referrer.
I found this in the tastypie source code. Basically implies that HTTP POST is not supported by SessionAuthentication.
class SessionAuthentication(Authentication):
"""
An authentication mechanism that piggy-backs on Django sessions.
This is useful when the API is talking to Javascript on the same site.
Relies on the user being logged in through the standard Django login
setup.
Requires a valid CSRF token.
"""
def is_authenticated(self, request, **kwargs):
"""
Checks to make sure the user is logged in & has a Django session.
"""
# Cargo-culted from Django 1.3/1.4's ``django/middleware/csrf.py``.
# We can't just use what's there, since the return values will be
# wrong.
# We also can't risk accessing ``request.POST``, which will break with
# the serialized bodies.
if request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
return request.user.is_authenticated()
So answering my own question here but if someone can explain it better and suggest a good way to do this, that would be great too.
Edit
I am currently using a workaround from https://github.com/amezcua/TastyPie-DjangoCookie-Auth/blob/master/DjangoCookieAuth.py which basically is a custom authentication scheme that fetches the session_id from the cookie and checks with the backend if it is authenticated. Might not be the most full proof solution but works great.

Authenticating users using pusher in django

I am a bit confused on how does the Authentication works in Django using pusher i want to implement a one-to-one chatting system so i guess i will be using private channels that requires authentication before you can subscribe to the channel ... i read there that the endpoint is the url you want pusher to POST to, i added a url to test if it is working but every time the status returns 403 and it seems it doesn't enter the view i created to test it so any ideas ? here is a sample of my code :
message.html
var channel = pusher.subscribe('private-test');
channel.bind('message', function(data) {
var $message = $('<div class="message"/>').appendTo('#messages');
$('<span class="user"/>').text(data.user).appendTo($message);
$('<span/>').text(data.message).appendTo($message);
});;
Pusher.channel_auth_endpoint = 'test/';
Pusher.channel_auth_transport = 'ajax';
channel.bind('pusher:subscription_succeeded', function(status) {
alert(status);
});
channel.bind('pusher:subscription_error', function(status) {
alert(status);
});
Views.py:
def testUser(request,user_name):
print 'Test Passed'
return render_to_response('message.html', {
'PUSHER_KEY': settings.PUSHER_KEY,'channel_variable':request.user.id,'other_var':'3',
}, RequestContext(request))
when i checked the url it POSTs to, in my cmd i found it correct and it matched the one i put in urls.py but i still don't know why it does not enter my view
I don't know Django, but it seems highly likely that the framework is intercepting the call to prevent CSRF (Cross site resource forgery).
The Django docs talk about CSRF here:
https://docs.djangoproject.com/en/dev/ref/contrib/csrf/
As with a number of frameworks you'll need to provide a CSRF token as part of the XHR/AJAX call to the authentication endpoint, or override the framework interception (somehow).
Have a look at the auth section of the Pusher constructor options parameter. In there you'll find an example of how to pass a CSRF token.

Django returns 403 error when sending a POST request

when I'm using following Python code to send a POST request to my Django website I'm getting 403: Forbidden error.
url = 'http://www.sub.example.com/'
values = { 'var': 'test' }
try:
data = urllib.urlencode(values, doseq=True)
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
the_page = response.read()
except:
the_page = sys.exc_info()
raise
When I'm opening any other website it works properly.
example.com is Django website too, and it works properly too.
I think, that's Django config problem, can anyone tell me what should I do to provide access to my script?
Look here https://docs.djangoproject.com/en/dev/ref/csrf/#how-to-use-it.
Try marking your view with #csrf_exempt. That way, Django's CSRF middleware will ignore CSRF protection. You'll also need to use from django.views.decorators.csrf import csrf_exempt. See: https://docs.djangoproject.com/en/dev/ref/csrf/#utilities
Please be advised that by disabling CSRF protection on your view, you are opening a gate for CSRF attacks.
If security is vital to you then consider using #csrf_exempt followed by #requires_csrf_token (see: https://docs.djangoproject.com/en/dev/ref/csrf/#unprotected-view-needs-the-csrf-token). Then, in your script pass this token and that's it.
Does the view that you are posting to have a Django Form on it? If so, I wonder if it's giving a csrf error. I think that manifests itself as a 403. In that case, you'd need to add the {{ csrf_token }} tag. Just a thought.
The response is 403 because django requires a csrf token (included in the post data) in every POST request you make.
There are various ways to do this such as:
Acquiring the token from cookie and the method has been explained in article enter link description here
or
You can access it from DOM using {{ csrf_token }}, available in the template
So now using the second method:
var post_data = {
...
'csrfmiddlewaretoken':"{{ csrf_token }}"
...
}
$.ajax({
url:'url',
type:'POST'
data:post_data,
success:function(data){
console.log(data);
},
error:function(error){
console.log(error);
}
});
Or you can allow the permission to make this post request.
Note: Should be used in the cases where you don't need to authenticate the users for posting anything on our server, say, when a new user registers for the first time.
from rest_framework.permissions import AllowAny
class CreateUser(APIView):
permission_classes = (AllowAny,)
def post(self, request, format=None):
return(Response("hi"))
Further Note that, If you want to make that post request form a different domain (in case when the front of the application is in React or angular and the backend is in Django), make sure the add following in the settings file:
Update the INSTALLED_APPS to use 'coreHeaders' :
INSTALLED_APPS = [
'corsheaders',
]
White list your front end domain by adding following to settings file again:
CORS_ORIGIN_WHITELIST = (
'localhost:8080',
)
Django documentation provides several ways to ensure that CSRF tokens are included. See https://docs.djangoproject.com/en/1.11/ref/csrf/ for details.
I got this error when an authentication Token was expired or when no Token was sent with the request. Using a renewed token fixed the problem.
curl -X POST -H "Authorization: Token mytoken" -d "name=myname&age=0" 127.0.0.1:8000/myapi/
or
curl -X POST -H "Authorization: JWT mytoken" -d "name=myname&age=0" 127.0.0.1:8000/myapi/
depending on Token type.
I too had this problem, because I Tried to access the Main endpoint from another endpoint using '../url' URL Jumping.
My Solution was to add another path for the same viewset;
router.register('main/url',ViewSet,'name');
router.register('secondary/url',ViewSet,'name')
But in Your Case You are Trying to access it from a completely different Location, From Django's Point of view So You need to mark you ViewSet with #crsf_exempt middleware which will Disable Security Protocols Related to CRSF.

Categories