HTTP 403 in django rest framework - python

So I have a function based view that I am using along with Django rest framework which looks like this :
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import permission_classes
#api_view(['GET'])
#permission_classes((IsAuthenticated, ))
def example_view(request):
.....
<business logic>
.....
This works as expected and gives a HTTP 401 if a user with insufficient privileges tries to access a URL bound to this view .However,due to how the front end angular is set up,what I need is to display a HTTP_403(forbidden).I went through the DRF documentation and could not see any already defined permission class that I can use as a decorator..What would be the best way of implementing this ?

So I found a solution to this.The IsAuthenticated class has very specific rules about emitting a 401 versus a 403 and they are as follows:
The request was successfully authenticated, but permission was denied. — An HTTP 403 Forbidden response will be returned.
The request was not successfully authenticated, and the highest priority authentication class does not use WWW-Authenticate headers. — An HTTP 403 Forbidden response will be returned.
The request was not successfully authenticated, and the highest priority authentication class does use WWW-Authenticate headers. — An HTTP 401 Unauthorized response, with an appropriate WWW-Authenticate header will be returned.
So inorder to force DRF to emit a 403 always just use an authentication class which does not use WWW-Authenticate headers.This is what i finally ended up with :
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import permission_classes
from rest_framework.decorators import authentication_classes
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
#api_view(['GET'])
#authentication_classes((SessionAuthentication, BasicAuthentication))
#permission_classes((IsAuthenticated, ))
def example_view(request):
.....
<business logic>
.....

Related

What is the difference between permission_classes and authentication_classes in django rest framework generic API Views

I know what permissions and authentication means. Authentication identifies the user and permissions decide what action can be done by the user. I have implemented a token authentication in django rest framework.
The problem is when i specify both permission_classes and authentication_classes in ListAPIView i cant log in to the account in browsable page.
It only works when i delete authentication classes in the view.
The following view doesn't raise any exception, but gives {
"detail": "Authentication credentials were not provided."
} response. Also not logging in.
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (permissions.IsAdminUser,)
authentication_classes = [TokenAuthentication]
But after deleting the authentication_classes = [TokenAuthentication] the page works.
PS: curl and httpie works without any problem.
So what could be the issue here? What is the difference between those two classes?
After removing TokenAuthentication class you are able to access the api because then drf is using session authentication and browser handles the sessions for you. When you use TokenAuthentication then you need to add token in header of request which is not done by browser. thats why you are getting { "detail": "Authentication credentials were not provided." }.
try this api from postman by adding the token in headers then it will work.
headers will be like:-
Authorization: Token your-token
check this for token authentication.
https://www.django-rest-framework.org/api-guide/authentication/
and make sure user making the api call should be admin because you have specified IsAdminUser permission.

Is this view in django secure?

I am building a RESTAPI in Django using the following library called drf-firebase-token-auth. The client sends a get request with the token_id available in the Authorization header, and the library is expected to handle authentication in Firebase automatically.
But the problem is if I do a simple get request without headers like curl <url+endpoint> the library doesn't verify if there's a an Authorization header and try to get a token for verification as if the request happens successfully as if drf-firebase-token-auth didn't raised it to be a problem. So I handled an exception whereas if the get request doesn't have an an authorization header, then raise an exception. But is it secure?
from datetime import datetime
from rest_framework import status
from rest_framework.decorators import (
api_view,
)
from rest_framework.response import Response
# Create your views here.
#api_view(["GET"])
def check_token(request):
# if token is valid response with current server time
try:
if request.META["HTTP_AUTHORIZATION"]:
date = datetime.now()
return Response(
data="Server time is: " + str(date), status=status.HTTP_200_OK
)
except:
return Response(data="Authorization header unavailable")

Django: Can we enforce POST request in DRF without requiring csrftoken? [duplicate]

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.

Token in query string with Django REST Framework's TokenAuthentication

In an API built with Django REST Framework authentication can be done using the TokenAuthentication method. Its documentation says the authentication token should be sent via an Authorization header.
Often one can send API-keys or tokens via a query string in order to authenticate, like https://domain.com/v1/resource?api-key=lala.
Is there a way to do the same with Django REST Framework's TokenAuthentication?
By default DRF doesn't support query string to authenticate, but you can easily override their authenticate method in TokenAuthentication class to support it.
An example would be:
class TokenAuthSupportQueryString(TokenAuthentication):
"""
Extend the TokenAuthentication class to support querystring authentication
in the form of "http://www.example.com/?auth_token=<token_key>"
"""
def authenticate(self, request):
# Check if 'token_auth' is in the request query params.
# Give precedence to 'Authorization' header.
if 'auth_token' in request.query_params and \
'HTTP_AUTHORIZATION' not in request.META:
return self.authenticate_credentials(request.query_params.get('auth_token'))
else:
return super(TokenAuthSupportQueryString, self).authenticate(request)
class QueryStringBasedTokenAuthentication(TokenAuthentication):
def authenticate(self, request):
key = request.query_params.get('auth_token').strip()
if key:
return self.authenticate_credentials(key)
return False
DRF has TokenAuthentication which looks for token in header. The method authenticate_credentials takes care verifying the token.
As of 2018 using Django Rest Framework you can create your own Authentication class see http://getblimp.github.io/django-rest-framework-jwt/#extending-jsonwebtokenauthentication
class JSONWebTokenAuthenticationQS(BaseJSONWebTokenAuthentication):
def get_jwt_value(self, request):
return request.QUERY_PARAMS.get('jwt')
Then in the APIView class add
authentication_classes = (JSONWebTokenAuthenticationQS,)
Or
#authentication_classes((JSONWebTokenAuthenticationQS,))
On the view function.
Following up on Scott Warren's answer. That was a step in the right direction, because the DRFJWT docs don't include the important authentication_classes line. But with that, as noted in Issue 441 there is a big problem that you can't mix the JWT authentication methods. I ended up with:
class JSONWebTokenAuthenticationQS(JSONWebTokenAuthentication):
def get_jwt_value(self, request):
return request.GET.get('jwt') or JSONWebTokenAuthentication.get_jwt_value(self, request)
which so far seems to work well. It uses JSONWebTokenAuthentication instead of the Base class, because it has to in order to use the original get_jwt_value method.
refer answer of OmriToptix and Scott Warren and others website 1 and 2
for now most case use JSONWebTokenAuthentication, so now should override its get_jwt_value, full code is:
# from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.authentication import get_authorization_header
class JWTAuthByQueryStringOrHeader(JSONWebTokenAuthentication):
# class JWTAuthByQueryStringOrHeader(BaseJSONWebTokenAuthentication):
"""
Extend the TokenAuthentication class to support querystring authentication
in the form of "http://www.example.com/?jwt_token=<token_key>"
"""
def get_jwt_value(self, request):
# Check if 'jwt_token' is in the request query params.
# Give precedence to 'Authorization' header.
queryParams = request.query_params
reqMeta = request.META
if ('jwt_token' in queryParams) and ('HTTP_AUTHORIZATION' not in reqMeta):
jwt_token = queryParams.get('jwt_token')
# got jwt token from query parameter
return jwt_token
else:
# call JSONWebTokenAuthentication's get_jwt_value
# to get jwt token from header of 'Authorization'
return super(JWTAuthByQueryStringOrHeader, self).get_jwt_value(request)
here save above code to apps/util/jwt_token.py, then NOT FORGET add related Django settings:
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'apps.util.jwt_token.JWTAuthByQueryStringOrHeader',
...
),
...
}
now frontend/web side can call api like this:
http://localhost:65000/api/v1/scripts/3d9e77b0-e538-49b8-8790-60301ca79e1d/script_word_export/?jwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMWVkMGEwZDgtMmFiYi00MDFkLTk5NTYtMTQ5MzcxNDIwMGUzIiwidXNlcm5hbWUiOiJsc2R2aW5jZW50IiwiZXhwIjoxNTMxOTAyOTU0LCJlbWFpbCI6InZpbmNlbnQuY2hlbkBuYXR1cmxpbmcuY29tIn0.wheM7Fmv8y8ysz0pp-yUHFqfk-IQ5a8n_8OplbYkj7s
to pass jwt_token into server side, get authorized to download/export the file.
while still support original method pass jwt token inside header 'Authorization':
POST http://localhost:65000/api/v1/scripts/3d9e77b0-e538-49b8-8790-60301ca79e1d/script_word_export/
Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMWVkMGEwZDgtMmFiYi00MDFkLTk5NTYtMTQ5MzcxNDIwMGUzIiwidXNlcm5hbWUiOiJsc2R2aW5jZW50IiwiZXhwIjoxNTMxOTAyOTU0LCJlbWFpbCI6InZpbmNlbnQuY2hlbkBuYXR1cmxpbmcuY29tIn0.wheM7Fmv8y8ysz0pp-yUHFqfk-IQ5a8n_8OplbYkj7s

Django REST Framework CSRF Failed: CSRF cookie not set

I am using the django rest framework to perform API calls via IOS
and I get the following error
"CSRF Failed: CSRF cookie not set."
Here's my django API code:
class LoginView(APIView):
"""
List all snippets, or create a new snippet.
"""
#csrf_exempt
def get(self, request, format=None):
startups = Startup.objects.all()
serializer = StartupSerializer(startups, many=True)
return Response(serializer.data)
#csrf_exempt
def post(self, request, format=None):
profile = request.POST
....
What can I do?
If anyone is still following this question, the direct answer is that you need to use the decorator on the view method itself. The get and post methods defined on the APIView class just tell DRF how the actual view should behave, but the view method that the django router expects is not actually instantiated until you call LoginView.as_view().
Thus, the solution is to add the csrf_exempt decorator to urls.py. It might look as follows:
#file: urls.py
from django.conf.urls import patterns, url
from django.views.decorators.csrf import csrf_exempt
import views
urlpatterns = patterns('',
url('^login/$', csrf_exempt(views.LoginView.as_view())),
...
)
However, as Mark points out above, csrf protection is important to prevent your sessions from being hijacked. I haven't worked with iOS myself, but I would look into using django's cookie-based csrf tokens. You can use the ensure_csrf_cookie decorator to make django send a csrftoken cookie with a response, and your POST requests will validate as long as you include that token as an X-CSRFToken header.
I've had the same issue. My problem was that I forgot to put .as_view() in urls.py on MyAPIView. So it have to be like:
url(r'$', GetLikesAPI.as_view(), name='list')
not:
url(r'$', GetLikesAPI, name='list')
The problem you encounter here is that django for processing your view is using whatever as_view() method will return, not directly method get() or post().
Therefore you should decorate your class-based view in one of following ways:
In urls.py
urlpatterns = patterns('',
url('^login/$', csrf_exempt(views.LoginView.as_view())),
...
)
or on dispatch() method (pre django 1.9)
from django.utils.decorators import method_decorator
class LoginView(APIView):
#method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
...
or on class view itself (from django 1.9)
from django.utils.decorators import method_decorator
#method_decorator(csrf_exempt, name='dispatch')
class LoginView(APIView):
...
For GETs, you shouldn't be modifying data, so a CSRF isn't required.
If you are modifying data with a POST, then you SHOULD have a CSRF if you are using session based authentication. Otherwise, you're opening up a security hole. Even though you think your Django server is going to be servicing iPhone apps, there's nothing to stop someone with your app from sniffing the packets on the traffic to your server, and then reverse engineering access to the server with other kinds of web clients. For this reason, Django Rest Framework requires a CSRF in some cases. This is mentioned in the Django rest framework documentation.
The path around this requirement for POSTs is to not use session authentication. For example, you could use BasicAuthentication over HTTPS. With this authentication mechanism, you should use HTTPS to prevent credentials from being passed in the clear with every request.
This is an old question but something we ran into recently.
DRF disables CSRF by default, unless using session authentication. By default NSURLconnection is set up to handle cookies. You need to explicitly tell the iOS app to not use cookies. Then you can keep using session auth if needed and not have to csrf exempt your views.
urlpatterns = patterns('',
url('^login/$', csrf_exempt(views.LoginView.as_view())),
...
)
Guys. I had the same error and spent a lot of time just for finding that:
1) I had another router with 'login' and there i missed '$'. I mean sometimes you can forget something in routing and get this error.
In my case it happend because I sent put request to url='http://example.com/list/5' without slash at the end. When I changed url to url='http://example.com/list/5/' all started to work.

Categories

Resources