Django Rest Framework - Using Session and Token Auth - python

I am trying to get this working but don't know if this is possible. It should be doing it like this.
I developed a web app using Django + Rest-Framework + jQuery, and I want to have an external application to consume the same REST API, using JWT Tokens for authentication.
My current config is like this.
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_RENDERER_CLASS': [
'rest_framework.renderers.JSONRenderer',
]
}
SIMPLE_JWT = {
'AUTH_HEADER_TYPES': ('Bearer',),
}
views.py
class ListFileView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
user = request.user
if user:
obj = Foo.objects.filter(owner=user)
serializer = FooSerializer(obj, many=True)
data = serializer.data
return Response(data, status=status.HTTP_200_OK)
return Response({'detail': 'You have no access to files.'}, status=status.HTTP_400_BAD_REQUEST)
The tricky part is that when I use:
permission_classes = (IsAuthenticated,)
I can perform ajax calls from an external application (using a valid JWT token), but jQuery calls from within the app (with an authenticated user) fail with:
{"detail":"Authentication credentials were not provided."}
And on the other hand, if I use autentication_classes instead of permission_classes:
authentication_classes = (SessionAuthentication, BasicAuthentication)
I can perform ajax calls from within the web app using jQuery, but external calls fail with the same 403 error.
I tried using both like this:
class ListFileView(APIView):
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
...
but the external calls are also rejected.
Is it possible to have these two types of Auth working together in the same class view, or should I separate into two endpoints?
EDIT
Example calls from within the app with jQuery:
<script type="text/javascript">
function loadTblDocs() {
$("#tbl-docs > tbody").empty();
$.ajaxSetup({
headers: { "X-CSRFToken": '{{csrf_token}}' }
});
$.ajax({
type: 'GET',
contentType: "application/json; charset=utf-8",
url: "/api/list/",
dataType: "json",
success: function (data) {
console.log(data);
}
});
};
</script>
And externally via VBA code:
Set web = CreateObject("WinHttp.WinHttpRequest.5.1")
web.Open "GET", "/api/list/", False
web.SetRequestHeader "Authorization", "Bearer <JWT_TOKEN_HERE>"
web.Send

I couldn't work out exactly what is going on in your case, because the behavior in the 3 cases you shared does not seem to be consistent, but going to explain simply how authentication and authorization is determined in DRF, this might help you figure the issue out.
You should be able to use two authentication classes with no problems. With DRF, authentication works like this:
At each request, DRF goes over the provided authentication classes, in the order they are defined. For each class, there are 3 cases:
If it can authenticate the request with the current class, DRF sets request.user. From this point on, this request is authenticated.
If no authentication credentials are present, DRF skips that class
If authentication credentials are present but invalid, such as an invalid JWT token in Authorization header, DRF raises an exception and returns a 403 response.
DRF views normally use the DEFAULT_AUTHENTICATION_CLASSES variable defined in the settings file, but if you provide them in a view, settings are overridden.
Authorization comes into play when you add permission_classes to your views. permission_classes control access to your views, so when you add IsAuthenticated to a view's permission classes, DRF rejects the request with a 403 response if you try to access that view without an authenticated user.
So in your initial case, your internal AJAX request failing in this case suggests that there is no authenticated user data in your request session. I do not know what could be causing this. Perhaps you do not update request session in your login view (Django's login method should do this automatically).
In your second case, you remove permission_classes from the view, so the view will serve the request regardless if there is an authenticated user in the request or not. So it is expected that your internal AJAX request succeeds here, but I do not know why your external request fails in this case.
In your third case, from the point of your internal AJAX request, the scenario seems to be the same as the first case, so I do not know why your internal AJAX request succeeds in this case but not in the first case. The external request failing here is expected because you added the IsAuthenticated permission class to the view, but did not include JWTAuthentication in the authentication_classes, so your request can not be authenticated with your JWT token here.

Related

How to store data into a model associated with the current user?

In my frontend i'm logging into another app's api in the browser, I'm then redirected back to my app, that hits a View in my backend which gets a code from the other app's api, sends code back in a post request then receives an access token and stores it in a model associated with the current user.
My problem is that after the user gives permission to other app in the browser it redirects back to my backend view without the users token in the header so if i have permissions_classes set it wont allow user to access that view... but if i take the permissions_classes off, the view won't know who the current user is.
View #1 that prepares the other app's API url:
class getAPIAuthURL(APIView):
authentication_class = [authentication.TokenAuthentication]
permission_class = [permissions.IsAuthenticated]
def get(self, request):
scopes = 'scopes'
url = Request('GET', 'https://accounts.api.com/authorize',
params={
'scope': scopes,
'response_type': 'code',
'redirect_uri': REDIRECT_URL,
'client_id': CLIENT_ID
}
).prepare().url
return Response(url, status=status.HTTP_200_OK)
View #2 that gets data and stores it in model (this is the REDIRECT_URL from previous view):
class APICallback(APIView):
authentication_class = [authentication.TokenAuthentication]
permission_class = [permissions.IsAuthenticated]
def api_callback(request, format=None):
code = request.GET.get('code')
if not code:
return Response({'Error': 'Code not found in request'}, status=status.HTTP_400_BAD_REQUEST)
response = post('https://accounts.api.com/api/token', data={
'code': code,
}).json()
print(response)
user = request.user
access_token = response.get('access_token')
token = APITokenModel(user=user, access_token=access_token)
token.save()
return redirect('frontend')
I have other Views that make requests and it has been able to get the token to know who the user is, but when this View is called I get a 401 Unauthorized error.
How do I let Django know the token I'm receiving from the other app's api belongs to the current user?
also... when I take off permissions and authentication class from the View it returns the user as Anonymous User
First, what authentication class are you using? You should know that your TokenAuthentication class uses the Authorization header in your request to authenticate you. If that's not been passed then you should fix that.
It would be worth knowing that you don't send auth tokens as GET and should not be sent as those. Unless of course you want to write an Authentication class of your own.
EDIT
In lieu of our discuss in the comments, try this redirect...
# import the class
from django.http import HttpResponseRedirect
# now redirect
return HttpResponseRedirect(redirect_to="url", headers=dict)

Unauthorized response to POST request in Django Rest Framework with JWT Token

I am building a REST API with Django Rest Framework. I currently have an issue where some of my endpoints return HTTP 401 Unauthorized, whereas the vast majority of my endpoints return correct responses. For authentication I am using JWT tokens with djangorestframework-simplejwt.
I've configured Django to use token auth with djangorestframework-simplejwt.
# rest framework config settings
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
# 'rest_framework.permissions.AllowAny',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
The vast majority of my endpoints return valid data when I pass a valid access token in the request. If I do not send a valid token, I receive a HTTP 403.
On the other hand, I have some custom API views which return a HTTP 401 regardless of whether I pass a valid token or not.
I have included the code to one of my problematic views below.
class CheckDifferentialView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
authentication_classes = [TokenAuthentication]
serializer_class = QuizDifferentialSerializer
def post(self, request, *args, **kwargs):
"""
A function to check a quiz question and to update a user's record of questions answered
"""
print(request.META)
if 'answer' not in request.data:
return JsonResponse({'Error': 'answer not found in request'}, status=status.HTTP_400_BAD_REQUEST)
answer = get_object_or_404(Differential, pk=request.data['answer'])
serializer = QuizDifferentialSerializer(answer)
if answer.is_correct:
pass
# record correct results
else:
pass
# record failed result
return Response(serializer.data, status=status.HTTP_200_OK)
And here is my script which I am using to test my API
import requests
import json
POST_LOGIN_URL = 'http://localhost:8000/api/v1/token/'
POST_URL= 'http://localhost:8000/api/v1/check_differential'
REQUEST_URL = 'http://localhost:8000/api/v1/users'
with requests.Session() as session:
post = session.post(POST_LOGIN_URL, json={"username": "j", "monkey": "aphextwin21"})
token = json.loads(post.text)['access']
headers = {'Authorization': 'Bearer ' + token}
r = session.post(POST_URL, headers=headers, json={"answer": "2"})
# r = session.get(REQUEST_URL, headers=headers)
print(token)
print(r.text, r.status_code)
The desired behaviour is that if I send a POST request with a valid token to this endpoint that is would authorise me and carry on with its day. If no Authorization header with a valid access token is given, then I expect it to deny the request.
Update
Enthusiastic Martin kindly point out that
authentication_classes = [TokenAuthentication]
Was overriding the defaults found in my settings file. I was not aware that as far as Django is concerned TokenAuthentication and JWTAuthentication are treated differently. Now I know.
After removing the authentication_classess = [TokenAuthentication] from my views, the views are working as they should.
The view's authentication class is explicitly set to TokenAuthentication only. It wont work with JWT token.
authentication_classes = [TokenAuthentication]
You either remove this to let the default classes handle it or change it to accept JWTAuthentication.

How to process Ajax Data in Python (Django)

I want to push front end data (Form inputs) to the server via Ajax. For this, I created an Ajax post request but I'm very unsteady...
At my first attemps, I constantly receive errors by python
Ajax call:
//Get journey time for the stated address
jQuery.ajax({
type: 'post',
url: 'http://127.0.0.1:8000/termin/get-journey-time/',
data: {
'method': 'get_journey_time',
'mandant_id': 1,
'customer_address': customer_address,
'staff_group': staff_group_id
},
error: function (jqXHR, textStatus, errorThrown) {
console.log("Error")
},
timeout: 120000,
});
I've created a view in Python, in which I want to do something (views.py)
class get_journey_time(generics.ListAPIView):
"""
Handle Ajax Post to calculate the journey time to customer for the selected staff group
"""
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
print(request)
In my url route file I have this code lines (urls.py)
urlpatterns = [
XXXXXXXXXXXXXXXXXXXXXXXXX,
path('termin/get-journey-time/', views.get_journey_time.as_view()),
XXXXXXXXXXXXXXXXXXXXXXXXX,
XXXXXXXXXXXXXXXXXXXXXXXXX,
]
I got the Error code 500:
Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<class 'NoneType'>`
Is there a mistake in my approach, did I miss anything or is it completely crap?
Define renderer classes and parser classes in settings.py.
Note: You can define many of these (based on requirements and needs) but here we only need JSON related.
As a reference, you can check my repo's this file https://github.com/hygull/p-host/blob/master/src/pmt_hostel_app/views.py. I have used function based views, just ignore the code inside it and focus on request.data and also check related HTML files.
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
)
}
In this way you will be able to access the posted data in the form of dictionary which can be obtained using request.data in views.
Finally, return Response from the post() method. By default return type of function/method is None and you are just printing the request.
Check the below mentioned links, it will help you a lot.
https://www.django-rest-framework.org/api-guide/renderers/
https://www.django-rest-framework.org/api-guide/parsers/
In client code, I mean in JavaScript code, define a success callback as well (you have just defined error callback).
Please comment, if you are stuck.
you can do it like this
from rest_framework.response import Response
from rest_framework.views import APIView
class get_journey_time(APIView):
# ListAPIView is used for read-only endpoints
#
"""
Handle Ajax Post to calculate the journey time to customer for the selected staff group
"""
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
# you can get the posted data by request.data
posted_data = request.data
data = {"test": "test"}
return Response(data)
you can get the posted data and use serializers. you can start learning playing with serializers from here
Example serializer code can be like this
from rest_framework import serializers
class DummySerializer(serializers.Serializer):
name = serializers.CharField()
mobile_number = serializers.CharField(required=False)
and then use it in post method of your get_journey_time class

Run django api from postman: CSRF verification failed

I'm trying to run an api using postman. My application is developed in django 1.11.6 using python 3.5.
My app is installed on an ubuntu server. I have no login mechanism to create a csrf token.
These are the steps that I follow:
Click on "import" tab on the upper left side.
Select the Raw Text option and paste my cURL command.
Hit import and I have the command in your Postman builder
Press send button.
My curl command is:
curl -i -H 'Accept: application/json; indent=4' -X POST https://127.0.0.1/users/:register/ -d "id=111&firstname=zinonas&yearofbirth=2007&lastname=Antoniou&othernames="
The error I get is Forbidden (403) - CSRF verification failed. Request aborted.
When I run the curl command via cygwin, it's working properly.
This is the view function that I'm using:
class ApiUserRegister(APIView):
permission_classes = ()
serializer_class = RegisterUserSerializer
def post(self, request):
serializer = RegisterUserSerializer(data=request.data)
# Check format and unique constraint
serializer.is_valid(raise_exception=True)
data = serializer.data
if User.objects.filter(id=data['id']).exists():
user = User.objects.get(id=data['id'])
is_new = "false"
resp_status = status.HTTP_200_OK
else:
user = User.objects.create(id=data['id'],
firstname=data['firstname'],
yearofbirth=data['yearofbirth'],
lastname=data['lastname'],
othernames=data['othernames'])
user.save()
is_new = "true"
resp_status = status.HTTP_201_CREATED
resp = {"user": serializer.get_serialized(user),
"isnew": is_new}
return Response(resp, status=resp_status)
In settings.py I have:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
)
}
Try this.
from django.views.decorators.csrf import csrf_exempt
class ApiUserRegister(APIView):
permission_classes = ()
serializer_class = RegisterUserSerializer
#csrf_exempt
def post(self, request):
serializer = RegisterUserSerializer(data=request.data)
To make AJAX requests, you need to include CSRF token in the HTTP header, as described in the Django documentation.
1st option
2nd option
simple just make sure to put as_view()
urlpatterns = [
path('sign_up', views.SignUp.as_view()),
]
update your class to be like this
from braces.views import CsrfExemptMixin
class your_class(CsrfExemptMixin, ......yours_here)
def post(...):
[....]
this will tell django to allow requests without csrf
Django sets csrftoken cookie on login. After logging in, we can see the csrf token from cookies in the Postman. (see image)
CSRFtoken from cookies
We can grab this token and set it in headers manually.
But this token has to be manually changed when it expires. This process becomes tedious to do it on an expiration basis.
Instead, we can use Postman scripting feature to extract the token from the cookie and set it to an environment variable. In Test section of the postman, add these lines.
var xsrfCookie = postman.getResponseCookie("csrftoken"); postman.setEnvironmentVariable('csrftoken', xsrfCookie.value);
This extracts csrf token and sets it to an environment variable called csrftoken in the current environment.
Now in our requests, we can use this variable to set the header.(see image)
Set {{csrftoken}} in your header
When the token expires, we just need to log in again and csrf token gets updated automatically.
Thanks to #chillaranand from hackernoon.com to original post
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.csrf import csrf_protect
#csrf_protect
#csrf_exempt
def home(request):
Add "#csrf_protect, #csrf_exempt" Before the method
In urls file, try this:
urlpatterns = [
url(r'^your_uri/', views.YourView.as_view()),
]
this will tell django to allow requests without csrf

Django Rest Framework Authentication Errors

I'm making an angular app that authenticates in a django site using a rest api. In the doc of drf they state that a denied user authentication will result in two error, HTTP 401 and HTTP 403, as follows:
When an unauthenticated request is denied permission there are two different error codes that may be appropriate.
HTTP 401 Unauthorized
HTTP 403 Permission Denied
Howenever, when I try to authenticate with some dummy data, that is wrong on purpose, I get error response HTTP 400 and with non_field_errors. What is the issue here?
First I do a simple authentication with the username and the password, this made to get the user token, that will be needed on other operations in the site.
I'm using a method to obtain an expering auth token, you can see it below:
class ObtainExperingAuthToken(ObtainAuthToken):
def post(self, request, *args, **kargs):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
print serializer
user = UserProfile.objects.get(email=serializer.data['username'])
token, created = Token.objects.get_or_create(user=user)
utc_now = timezone.now()
if not created and token.created < utc_now - datetime.timedelta(hours=24):
token.delete()
print serializer.data
token = Token.objects.create(user=serializer.data['user'])
token.created = datetime.datetime.utcnow()
token.save()
groups = [group.name for group in user.groups.all()]
response_data = {
'email': user.email,
'token': token.key,
'groups': groups
}
return HttpResponse(json.dumps(response_data), content_type='application/json')
return HttpResponse(serializer.errors, status=400)
#Edit
The value for REST_FRAMEWORK
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'accounts.authentication.ExpiringTokenAuthentication',
],
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
'rest_framework.parsers.FileUploadParser'
]
}
Firstly although it's not very clear from your explanation, but I'm guessing you are getting 400 error in the api responsible for providing user access token. Since obviously there would be no authentication on this api (except Client Authentication maybe), so you are genuinely getting bad request error since you are providing it with invalid data as you mentioned yourself.
Authentication error comes when you access an api that doesn't allow unauthenticated access, which shouldn't be the case for the api that actually provides the authentication token.
Secondly to get Authentication error you need to add Authentication Classes to either REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] or to the AUTHENTICATION_CLASSES variable of the view class.

Categories