JWT authentication doesn't work for custom controller in Django - django

I am using the Django Rest Framework in my Python app, and am using JSON Web Token Authentication (DRF JWT) for the api authentication.
My problem comes when I am building a custom controller. I pointed a specific URL to a function in my calculations.py file that I created. Following are how they look.
urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
from rest_framework import routers
from app.serializers import xxxViewSet, yyyViewSet
from app.calculations import getReturns
router = routers.DefaultRouter()
router.register(r"xxx", xxxViewSet)
router.register(r"yyy", yyyViewSet)
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^api/auth/token/$', 'rest_framework_jwt.views.obtain_jwt_token'),
url(r'^api/auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^api-token-verify/', 'rest_framework_jwt.views.verify_jwt_token'),
url(r'^api/', include(router.urls)),
**url(r'^getReturns/', getReturns),**
)
calculations.py
from django.http import HttpResponse
from .models import xxx, yyy, zzz, aaa
def getReturns(request):
data = request.GET('data')
**running calculations here on data and giving out response**
return HttpResponse(response)
serializers.py
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework import routers, serializers, viewsets, permissions
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from .models import xxx, yyy, zzz, aaa
class xxxSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = xxx
fields = ('id', 'name')
class xxxViewSet(viewsets.ModelViewSet):
authentication_classes = [SessionAuthentication, BasicAuthentication, JSONWebTokenAuthentication]
permission_classes = [permissions.IsAuthenticated, permissions.IsAdminUser]
queryset = xxx.objects.all()
serializer_class = xxxSerializer
The above serializers.py file contains serializer classes for all my models, and also viewsets for the same. I haven't yet transferred the viewsets into views.py, so that file is empty for now.
Anyway, my calculations.pyis separate from these files, and the function defined in this file is directly being called by the '/getReturns/' URL without going through a view. How do I incorporate the functions defined in my calculations file into a viewset so that my authorization classes are called before the function gets executed?

I started doing this in comments and it was too long. Generally, you haven't really provided enough code to assist properly, but here's my crack anyway. It's not obvious which version/implementation of Django JWT you're using (there are a few), how you're authorising your views, or whether your calculations.py file is a view or something else. (If it's something else, I'd authorise in the view, and call it from there.)
Why are you unable to send a POST? Generally, Once you have the token in your front end, you can use from rest_framework.decorators import authentication_classes and #authentication_classes([JSONWebTokenAuthentication,]) wrapper on any function that needs authorisation.
That looks like this:
#authentication_classes([JSONWebTokenAuthentication,])
def function_here(arguments):
#function does stuff
How are you passing/trying to send the web token back to the application?
Presumably in CURL your initial auth to get a token looks something like:
curl -X POST -d "username=admin&password=abc123
after that you get (if you're using rest_framework_jwt) the token back:
{JWTAuthorization: YourTokenHere}.
After that, to return it to DRF protected pages (assuming they're wrapped, as above, or have a similar protection) - you haven't outlined how you're authorisation - then from the docs you do:
curl -H "Authorization: JWT <your_token>" http://localhost:8000/protected-url/
If you're generating the call in Angular or similar, then it's the same - you need to pass it in the headers.
Edit: I'd also note you've drastically increased the amount of code here since my original answer. Fundamentally though, you need to check the auth classes you're declaring; the easiest way is as specified above.

Related

Expected view Equation1View to be called with a URL keyword argument named "pk"

I'm trying to get data directly from request body in json. Making calculations in API will be main part of my Django REST Api.
In the future I'm going to add database from which I'll be getting some data that will be used in calculations.
I'm having a problem with error AssertionError: Expected view Equation1View to be called with a URL keyword argument named "pk". Fix your URL conf, or set the 'lookup_field' attribute on the view correctly.
I don't know where should I put the pk parameter.
Using the api below I should be able to send {"name":"xxx", value:1.23} in request body and get {"emission":1.23} in json response. Ofcourse it will be more complicated in future as getting more data from request body and connecting it with data from db but now I have to make it simple.
api urls.py:
from .views import Equation1View
from django.urls import path
urlpatterns = [
path('equation/1', Equation1View.as_view())
#path('equation/2'),
#path('equation/3'),
]
views.py
from aiohttp import request
from rest_framework import generics
from .serializers import EmissionSerializer, EquaSerializer
# Create your views here.
class Equation1View(generics.RetrieveAPIView):
queryset = ''
serializer_class = EquaSerializer(context={'request':request})
serializers.py
from rest_framework import serializers
class EquaSerializer(serializers.Serializer):
emission = serializers.SerializerMethodField('perform_equation')
def perform_equation(self):
request = self.context.get("request")
if request and hasattr(request, 'name') and hasattr(request, 'value'):
return request.value
RetrieveAPIView is generic api view to retrieve specific data from the model and that data is retrieved based on the pk that is passed through the url so change your urls.py as
from .views import Equation1View
from django.urls import path
urlpatterns = [
path('equation/<int:pk>', Equation1View.as_view())
#path('equation/2'),
#path('equation/3'),
]

Query parameters in request on Django 3.0.3

I'm putting together an API and need to add query parameters to the URI like https://www.example.com/api/endpoint?search=term&limit=10.
My first question is, in Django 3.0, I'd need to use re-path to accomplish parsing out the various parameters, correct?
Secondly, the question is about convention. It seems like two of the three APIs I've been working with a lot lately us a convention like:
/api/endpoint?paramater1=abc&parameter2=xyz
Another uses something like:
/api/endpoint?$parameter1=abc&parameter2=abc
Looking at some past Django question related to this topic, I see stuff like:
/api/endpoint/?parameter1=abc&parameter2=xyz
Another post I read was suggesting that parameters should be separated with ;.
I guess I'm just curious what the "correct" convention should be either in terms of Django or general concensus.
Lastly, it seems to me what I'm trying to accomplish should be a GET request. The front-end sends the user defined parameters (section and startingPage) to the back-end where a PDF is generated matching those parameters. When it is generated, it sends it back to the FE. The PDFs are much too large to generate client-side. GET would be the correct method in the case, correct?
Well, I elected to go with the first convetion:
/api/endpoint?paramater1=abc&parameter2=xyz
Simply because the majority of APIs I work with use this convention.
For my files:
# urls.py
from django.urls import path
from .views import GeneratePDFView
app_name = 'Results'
urlpatterns = [
path('/endpoint',
GeneratePDFView.as_view(), name='generate_pdf')
]
# view.py
from django.conf import settings
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
from rest_framework.views import APIView
class GeneratePDFView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
if len(request.query_params) == 2:
data['id'] = {'id': request.user.id}
data['starting_page'] = request.query_params.get('parameter1')
data['data_view'] = request.query_params.get('parameter2')
serializer = GeneratePDFSerializer(data=data)
...

Django Rest Framework Custom Endpoints

I have recently inherited an API built with Django and DRF. I need to add some endpoints to the API but have never worked with Django or DRF before so I am trying to come up to speed as quickly as possible.
I am wondering how to do custom endpoints that don't just translate data too/from the backend database. A for instance might be an endpoint that reads data from the DB then compiles a report and returns it to the caller in JSON. But I suppose that right now the simplest method would be one that when the endpoint is hit just prints 'Hello World' to the log and returns a blank page.
I apologize if this seems basic. I've been reading through the docs and so far all I can see is stuff about serializers when what I really need is to be able to call a custom block of code.
Thanks.
if you want your REST endpoint to have all: GET, POST, PUT, DELETE etc. functionality then you have to register a route in your urls.py:
urls.py:
from rest_framework import routers
from django.urls import path, include
from . import views
router = routers.DefaultRouter()
router.register(r'hello', views.HelloWorldViewSet)
urlpatterns = [
# Wire up our API using automatic URL routing.
# rest_framework api routing
path('api/', include(router.urls)),
# This requires login for put/update while allowing get (read-only) for everyone.
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
now the url: /hello/ points to the HelloWorldViewSet.
in your views.py add the HelloWorldViewSet that will inherits from the rest_framework.viewsets.ViewSet class. You can override the ViewSet default class behavior by defining the following "actions": list(), create(), retrieve(), update(), partial_update(), destroy(). For displaying "hello world" on GET you only need to override list():
so in your views.py:
from rest_framework import viewsets
from rest_framework.response import Response
class HelloWorldViewSet(viewsets.ViewSet):
def list(self, response):
return Response('Hello World')
So, in your more advanced list() function you have to interact with the database, to retrieve the data you want, process it and create the report as a json serializable dictionary and return it as a Response object.
If you don't want to override the standard list action, you could instead add a new action to the HelloWorldViewSet let's call it report:
so in your views.py:
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import action
class HelloWorldViewSet(viewsets.ViewSet):
#action(detail=False)
def report(self, request, **kwargs):
return Response('Hello World')
I hope this is what you were looking for.
Note that you don't need django-rest-framework if you are not interested in POST, PUT, PATCH, DELETE, etc... you can simply add a path to your urls.py that points to a Django view function that returns a Django JsonResponse object containing your report.
One good option for you would be DRF actions. Docs here
The action allows you to choose a relevant view for what you wanna do, so you can just pop things into the API you inherited. No additional setup needed, they show up next to the regular routes.

django1.8 api - how to authenticate with a key

I want to allow the django users to use a key in the api urls for authentication.
I do not have OAUTH set up yet so I guess the key could be a sesion key or a digest key.
I'm having 2 problems.
I've tried sending this request:
http://192.166.166.11:8000/api?task=search&term=115&csrf_token=s69WAIZqlCTur1XZQr72QhCc7fzqaRtM
First of all, I've tried using the csrf_token but it does not work.
It takes me to the login page.
Secondly, I do not know how to retrieve csrf_token of other users (the admin is trying to get their csrf_tokens).
My attempt:
x = User.objects.get(username='someone')
x.get_session_auth_hash()
gives me the user's authentication hash but it is a different value.
Can someone please guide me get these two problems sorted out?
You are creating a token-based authentication. You already mentioned OAUTH as one option, and I strongly recommend using one of the existing implementations like django-oauth-toolkit. However, you can also create your own quick solution to create a token-based authentication.
Disclaimer: This is for demo purposes only. Do not copy it in any existing project. It will make your application vulnerable.
First, we create an additional model handling the authentication tokens:
/auth_tokens/models.py
from django.db import models
from django.conf import settings
import string, random
def random_string(length = 64, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for x in range(length))
class AuthToken(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
token = models.CharField(max_length=64, default=random_string)
/auth_tokens/middleware.py
from auth_tokens.models import AuthToken
class AuthTokenMiddleware:
def process_request(self, request):
token = request.GET.get('auth', None)
if not token:
return
token = AuthToken.objects.get(token=token)
request.user = token.user
return request
Including the middleware into your settings.MIDDLEWARE_CLASSES should enable you to add ?token=<token> to your URL to login your users.
I ended up using token authentication:
http://www.django-rest-framework.org/api-guide/authentication/
so I'd like to share the workflow.
First, you need to do the set up. In settings.py, modify INSTALLED_APPS and add REST_FRAMEWORK as in documentation.
Then you need to run python manage.py syncdb because it needs to add some tables.
Then, you need to add some urls to urls.py to route the api.
You can create and retrieve tokens using this code:
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=User.objects.get(username='john'))
print token.key
Lastly, you'll have to modify your view which depends on whether you're using a function based or class based view.
Here is a function based view I used:
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import authentication_classes, permission_classes
from rest_framework.decorators import api_view
#api_view(['GET', 'POST'])
#authentication_classes((TokenAuthentication,))
#permission_classes((IsAuthenticated,))
#login_required
def mybooks(request):
entries = Book.objects.all()
return render(request, 'mybooks.html', {'entries': entries})
Lastly, to test it out:
import requests
token = '243124c52f7583e320d043c4395bd99f63344035'
headers = {'Authorization' : 'Token {}'.format(token)}
page = requests.post('http://127.0.0.1:8000/mybooks/', headers=headers)
print page.content
Note that in my case I do not need define plain serialization since I have an advanced custom serialization and that is not the topic here.
Django doesn't provide API Keys out of the box.
Use API providers such as Tastypie to have this feature

Django Rest Framework remove csrf

I know that there are answers regarding Django Rest Framework, but I couldn't find a solution to my problem.
I have an application which has authentication and some functionality.
I added a new app to it, which uses Django Rest Framework. I want to use the library only in this app. Also I want to make POST request, and I always receive this response:
{
"detail": "CSRF Failed: CSRF token missing or incorrect."
}
I have the following code:
# urls.py
from django.conf.urls import patterns, url
urlpatterns = patterns(
'api.views',
url(r'^object/$', views.Object.as_view()),
)
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt
class Object(APIView):
#csrf_exempt
def post(self, request, format=None):
return Response({'received data': request.data})
I want add the API without affecting the current application.
So my questions is how can I disable CSRF only for this app ?
Note: Disabling CSRF is unsafe from security point of view. Please use your judgement to use the below method.
Why this error is happening?
This is happening because of the default SessionAuthentication scheme used by DRF. DRF's SessionAuthentication uses Django's session framework for authentication which requires CSRF to be checked.
When you don't define any authentication_classes in your view/viewset, DRF uses this authentication classes as the default.
'DEFAULT_AUTHENTICATION_CLASSES'= (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
),
Since DRF needs to support both session and non-session based authentication to the same views, it enforces CSRF check for only authenticated users. This means that only authenticated requests require CSRF tokens and anonymous requests may be sent without CSRF tokens.
If you're using an AJAX style API with SessionAuthentication, you'll need to include a valid CSRF token for any "unsafe" HTTP method calls, such as PUT, PATCH, POST or DELETE requests.
What to do then?
Now to disable csrf check, you can create a custom authentication class CsrfExemptSessionAuthentication which extends from the default SessionAuthentication class. In this authentication class, we will override the enforce_csrf() check which was happening inside the actual SessionAuthentication.
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
class CsrfExemptSessionAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
return # To not perform the csrf check previously happening
In your view, then you can define the authentication_classes to be:
authentication_classes = (CsrfExemptSessionAuthentication, BasicAuthentication)
This should handle the csrf error.
Easier solution:
In views.py, use django-braces' CsrfExemptMixin and authentication_classes:
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt
from braces.views import CsrfExemptMixin
class Object(CsrfExemptMixin, APIView):
authentication_classes = []
def post(self, request, format=None):
return Response({'received data': request.data})
Modify urls.py
If you manage your routes in urls.py, you can wrap your desired routes with csrf_exempt() to exclude them from the CSRF verification middleware.
import views
from django.conf.urls import patterns, url
from django.views.decorators.csrf import csrf_exempt
urlpatterns = patterns('',
url(r'^object/$', csrf_exempt(views.ObjectView.as_view())),
...
)
Alternatively, as a Decorator
Some may find the use of the #csrf_exempt decorator more suitable for their needs
for instance,
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
#csrf_exempt
def my_view(request):
return HttpResponse('Hello world')
should get the Job Done!
For all who did not find a helpful answer. Yes DRF automatically removes CSRF protection if you do not use SessionAuthentication AUTHENTICATION CLASS, for example, many developers use only JWT:
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
But issue CSRF not set may be occurred from some another reason, for exmple you not correctly added path to you view:
url(r'^api/signup/', CreateUserView), # <= error! DRF cant remove CSRF because it is not as_view that does it!
instead of
url(r'^api/signup/', CreateUserView.as_view()),
I tried a few of the answers above and felt creating a separate class was a little overboard.
For reference, I ran into this problem when trying to update a function based view method to a class based view method for user registration.
When using class-based-views (CBVs) and Django Rest Framework (DRF), Inherit from the ApiView class and set permission_classes and authentication_classes to an empty tuple. Find an example below.
class UserRegistrationView(APIView):
permission_classes = ()
authentication_classes = ()
def post(self, request, *args, **kwargs):
# rest of your code here
If you do not want to use session based authentication, you can remove Session Authentication from REST_AUTHENTICATION_CLASSES and that would automatically remove all csrf based issues. But in that case Browseable apis might not work.
Besides this error should not come even with session authentication. You should use custom authentication like TokenAuthentication for your apis and make sure to send Accept:application/json and Content-Type:application/json(provided you are using json) in your requests along with authentication token.
You need to add this to prevent default session authentication: (settings.py)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
Then: (views.py)
from rest_framework.permissions import AllowAny
class Abc(APIView):
permission_classes = (AllowAny,)
def ...():
You need to be absolutely sure, that you want to switch off CSRF protection.
Create file authentication.py and place it wherever you want in your project. For example, in folder session_utils.
Place this code in the file:
from rest_framework.authentication import SessionAuthentication
class SessionCsrfExemptAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
pass
When you want to make POST, PUT, PATCH or DELETE requests to your view be sure that you've changed SessionAuthentication to SessionCsrfExemptAuthentication from the new file. View example:
#api_view(["POST"])
#authentication_classes([SessionCsrfExemptAuthentication])
#permission_classes([IsAuthenticated])
def some_view(request) -> "Response":
# some logic here
return Response({})
This trick allow you to override method (pass) enforce_csrf and the new session authentication class will skip CSRF check.
✌️
I am struck with the same problem. I followed this reference and it worked.
Solution is to create a middleware
Add disable.py file in one of your apps (in my case it is 'myapp')
class DisableCSRF(object):
def process_request(self, request):
setattr(request, '_dont_enforce_csrf_checks', True)
And add the middileware to the MIDDLEWARE_CLASSES
MIDDLEWARE_CLASSES = (
myapp.disable.DisableCSRF,
)
My Solution is shown blow. Just decorate my class.
from django.views.decorators.csrf import csrf_exempt
#method_decorator(csrf_exempt, name='dispatch')
#method_decorator(basic_auth_required(
target_test=lambda request: not request.user.is_authenticated
), name='dispatch')
class GenPedigreeView(View):
pass
When using REST API POSTs, absence of X-CSRFToken request header may cause that error.
Django docs provide a sample code on getting and setting the CSRF token value from JS.
As pointed in answers above, CSRF check happens when the SessionAuthentication is used. Another approach is to use TokenAuthentication, but keep in mind that it should be placed first in the list of DEFAULT_AUTHENTICATION_CLASSES of REST_FRAMEWORK setting.
If you are using an exclusive virtual environment for your application, you can use the following approach without effective any other applications.
What you observed happens because rest_framework/authentication.py has this code in the authenticate method of SessionAuthentication class:
self.enforce_csrf(request)
You can modify the Request class to have a property called csrf_exempt and initialize it inside your respective View class to True if you do not want CSRF checks. For example:
Next, modify the above code as follows:
if not request.csrf_exempt:
self.enforce_csrf(request)
There are some related changes you'd have to do it in the Request class
This could also be a problem during a DNS Rebinding attack.
In between DNS changes, this can also be a factor. Waiting till DNS is fully flushed will resolve this if it was working before DNS problems/changes.
For me, using django 3.1.5 and django rest framework 3.12 the solution was way easier.
It happened to me that on a views.py file I had defined this two methods:
#api_view(['POST'])
#permission_classes((IsAuthenticated, ))
def create_transaction(request):
return Response(status=status.HTTP_200_OK)
def create_transaction(initial_data):
pass
On my urls.py:
urlpatterns = [
path('transaction', views.create_transaction, name='transaction'),
]
Django was picking the latest and throwing the error. Renaming one of the two solved the issue.
Code bellow would remove demand for CSRF. Even anon user would be able to send request.
from typing import List, Any
class Object(APIView):
authentication_classes: List = []
permission_classes: List[Any] = [AllowAny]
...
...
Removing CSRF check is not always the only (or best) solution. Actually, it's an important security mechanism for SessionAuthentication.
I was having the same issue when trying to authenticate with JWT and doing a POST request.
My initial setup looked like this:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
"django_cognito_jwt.JSONWebTokenAuthentication",
),
...
}
As SessionAuthentication was checked first in the list, the CSRF error was raised. My solution was as simple as changing the order to always check JWT auth first. Like this:
"DEFAULT_AUTHENTICATION_CLASSES": (
"django_cognito_jwt.JSONWebTokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
),
At the end, SessionAuthentication for me is only used in the django admin panel and 99% of the requests goes to the API that uses JWT auth.