Django Rest Framework APIView not CSFR Exempt - django

Making a POST requests to register a new user through postman returns 403 Forbidden, CSRF verification failed. Request aborted... Going by DRF documentation and knox auth documentation i have everything set up correctly. It appears that Django's SessionAuthentication is being activated even though i do not have it in my DEFAULT_AUTHENTICATION_CLASSES. I have tried every potential solution i could find but nothing is working. The app is a Django rest api with React front end. Any help would be greatly appreciated.
Authentication and Permission settings
'DEFAULT_AUTHENTICATION_CLASSES': (
'knox.auth.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
url calling the view as_view
re_path('auth/register', Register.as_view(), name='register'),
Class based Register view extending APIView which should handle csrf
class Register(APIView):
"""User Registration API View"""
def post(self, request, *args, **kwargs):
if request.method == 'POST':
serializer = RegistrationSerializer(data=request.data)
data = {}
if serializer.is_valid():
user = serializer.save()
data['response'] = 'Account registered successfully'
data['firstName'] = user.first_name
data['lastName'] = user.last_name
data['email'] = user.email
data['token'] = AuthToken.objects.get(user=user).key
return Response(data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Stack trace error
Forbidden (CSRF cookie not set.): /api/account/auth/register
[20/Jun/2020 12:15:14] "POST /api/account/auth/register HTTP/1.1" 403 2864
Update
I have found the issue and have added it as an answer below

For anyone else who may find themselves in this situation I hope this can be of some help. It appears that because I am integrating React with Django and using Reacts router the Django urls must be placed before the React urls in the base urls.py file otherwise React's router takes the request and runs it through its router, cannot find any matching urls and throws an error thus it never gets run through the Django api endpoints. This explains why i was getting CSRF errors as Django's SessionAuthentication was being hit through React router. After all the testing the answer was as simple as swapping two lines.
Previous Throwing Errors
urlpatterns = [
# Admin portal
re_path('admin/', admin.site.urls),
# React UI Entry
re_path('', include('frontend.urls'), name='frontend'),
# Rest API Urls
re_path('api/account/', include('account.api.urls'), name='account_api'),
]
Refactored No Errors
urlpatterns = [
# Admin portal
re_path('admin/', admin.site.urls),
# Rest API Urls
re_path('api/account/', include('account.api.urls'), name='account_api'),
# React UI Entry
re_path('', include('frontend.urls'), name='frontend'),
]

Related

Setup google login using Django rest framework plus React Js

views
class GoogleCreateAccount(APIView):
permission_classes = [permissions.AllowAny]
def post(self, request):
reg_serializer = RegisterSerializer(data=request.data)
if reg_serializer.is_valid():
new_user = reg_serializer.save()
if new_user:
# add these
r = requests.post('http://127.0.0.1:8000/auth/token', data={
'username': new_user.email,
'id_token': request.data['code'],
#'password': request.data['password'],
'client_id': '304129974707-79jslq7l318va16eacni8cveokn237g8.apps.googleusercontent.com',
'client_secret': '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',
'grant_type': 'id_token'
})
return Response(r.json(), status=status.HTTP_201_CREATED)
return Response(reg_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
urls.py
urlpatterns = [
path('auth/', include('drf_social_oauth2.urls',namespace='drf')),
path('googleregister/',GoogleCreateAccount.as_view()),
]
I was setting up an google login with django and react app i was setting it up based on the https://abhik-b.medium.com/step-by-step-guide-to-email-social-logins-in-django-5e5436e20591 above link. It is working on the bcakend as expected. But for getting the access token the parameters I giving is
{
clientid:
client secret:
grant type: password
username:
password:
}
But in the react it wasn't able to get the password parameter so it is not getting connected to the rest api call. I don't know exactly how to get this issue sorted. Please suggest me a best link or docs for setting up google login for django rest framework as backend and react js as frontend.
And is this possible to change the grant type to authorisation code and if so what needs to be changed?

Django (DRF) & React - Forbidden (CSRF cookie not set)

There are tens of questions that are essentially identical to the one I'm asking. However, none of their answers seem to be working for me.
I have a React front-end where I am using axios to send requests to the back-end. Example
const request = await axios.post('${BASE_URL}/logout/')
Most of the Django Rest Framework endpoints are made with ViewSets. However, I have a few that are custom and mostly made for authentication.
path('createaccount/', views.create_account),
path('me/', views.current_user),
path('logout/', views.logout),
path('login/', views.login),
path('resetpassword', views.reset_password),
For the development of this project I've included #csrf_exempt above these views because I didn't want to deal with it at the time. Now I'm nearing deployment and it's time to figure it out.
Some answers say I need to get a CSRF Token from Django which is stored in cookies and I need to pass that in the header of each request. Some answers say all I need to do is configure axios like
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "XCSRF-TOKEN";
And it will "just work". I've tried adjusting my CSRF_COOKIE_NAME to various values to get this to work too.
Some answers even say to keep #csrf_exempt but that sounds like a very, very bad idea.
Do I actually need to generate/get a CSRF cookie? Do I include it with every request? Or is it just a configuration of axios?
To make CSRF protection work you will need CSRF cookie sent from Django to
React as a response to some request (like login or sth else). It will set cookie using
Set-Cookie on frontend side. So make sure that you have a view that does that on Django side. If not, create a view that as response generates that token.
How Django (4.04) CSRF validation work (simplified, based on middleware/csrf.py):
gets CSRF token from cookie (so frontend needs to resend it back on
another request) - it might also get it from session but in case of
React I would not use it
def _get_token(self, request):
....
try:
cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME]
except KeyError:
return None
Compares that cookie CSRF token with non-cookie token from request:
def _check_token(self, request):
# Access csrf_token via self._get_token() as rotate_token() may have
# been called by an authentication middleware during the
# process_request() phase.
try:
csrf_token = self._get_token(request)
except InvalidTokenFormat as exc:
raise RejectRequest(f"CSRF cookie {exc.reason}.")
if csrf_token is None:
# No CSRF cookie. For POST requests, we insist on a CSRF cookie,
# and in this way we can avoid all CSRF attacks, including login
# CSRF.
raise RejectRequest(REASON_NO_CSRF_COOKIE)
# Check non-cookie token for match.
request_csrf_token = ""
if request.method == "POST":
try:
request_csrf_token = request.POST.get("csrfmiddlewaretoken", "")
except UnreadablePostError:
# Handle a broken connection before we've completed reading the
# POST data. process_view shouldn't raise any exceptions, so
# we'll ignore and serve the user a 403 (assuming they're still
# listening, which they probably aren't because of the error).
pass
if request_csrf_token == "":
# Fall back to X-CSRFToken, to make things easier for AJAX, and
# possible for PUT/DELETE.
try:
request_csrf_token = request.META[settings.CSRF_HEADER_NAME]
except KeyError:
raise RejectRequest(REASON_CSRF_TOKEN_MISSING)
token_source = settings.CSRF_HEADER_NAME
else:
token_source = "POST"
try:
request_csrf_token = _sanitize_token(request_csrf_token)
except InvalidTokenFormat as exc:
reason = self._bad_token_message(exc.reason, token_source)
raise RejectRequest(reason)
if not _does_token_match(request_csrf_token, csrf_token):
reason = self._bad_token_message("incorrect", token_source)
raise RejectRequest(reason)
As you can see you either need to include csrfmiddlewaretoken in POST request or include it in header with key: settings.CSRF_HEADER_NAME and value read from cookies on front-end side.
So for example you set withCredentials: true (to include initial cookie), read that initial CSRF cookie in React and add to header in axios request at specific key.
When in question, I would just debug request setting up breakpoints in this code of Django in middleware/csrf.py and you can trace what is missing and why CSRF validation fails.
I've got this problem once, I was using token authentication. That's how I solved it. But not sure If it is the best idea. I only used csrf_exempt for this view and all others views are viewsets.
#csrf_exempt
def get_current_user(request, *args, **kwargs):
if request.method == 'GET':
user = request.user
serializer = UserDataSerializer(user)
return JsonResponse(serializer.data, safe=False)
My middleware in settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
# 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.locale.LocaleMiddleware',
'oauth2_provider.middleware.OAuth2TokenMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'auditlog.middleware.AuditlogMiddleware',
]

How to test Django REST Framework Login Protected API using postman?

I need to test REST API using postman. API is build using Django REST Framework. Only login user can get access to API. I am not able to find how I can send login credentials using postman. Thanks in advance.
class ApiMemberGroupNameList(views.APIView):
permission_classes = (
permissions.IsAuthenticated,
RequiredOrgPermission,
RequiredOrgStaffMemberPermission)
def get(self, request, **kwargs):
pk = kwargs.get('pk')
hash = kwargs.get('hash')
member_obj = get_object_or_404(Member.objects.filter(org__hash=hash, pk=pk))
return Response(GroupListSerializer(member_obj.groups.all(), many=True).data)
You can use Basic Auth in POSTMAN. Refer to this screenshot:
You could change the HTTP Methods, URLs, Data Payload(under Body tab) etc in the POSTMAN console
UPDATE-1
After your comments, I tried to recreated the problem.What I did was:
created a view
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
class MySampleView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request):
return Response(data={"status": True})
Added to urls.py
urlpatterns = [
url(r'^mysampleview/$', MySampleView.as_view())
]
And my POSTMAN response are below:
Authorization Screenshot
Header Screenshot
My conclusion
You may enter wrong credentials, or something else. I would suggest you open a new POSTMAN tab and repeat the procedure, also try to login Django admin using the same credential which is already used in POSTMAN.
If you want to use Basic Authentification to test Django REST API, it must be allowed in Django REST Framework settings:
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
...

Why is request object not the same between a "native" view and a ModelViewSet?

I am building an API with Django Rest Framework (DRF) and enabled the authentication/registration through social apps.
For authenticating users via their social accounts I use Django rest-framework Social Oauth2 and it works like a charm. To be sure my user is logged in I created a very simple view in the views.py of my app:
def index(request):
return HttpResponse("is_anonymous: %s" % request.user.is_anonymous)
The result in the browser is the following (it means that the user is logged in):
is_anonymous: False
Now as I am building an API with DRF I may need to retrieve some data of the current user (from request.user) in one of my viewsets but in the following code, the result is not what I expected:
class HelloViewSet(viewsets.ModelViewSet):
queryset = Hello.objects.all()
serializer_class = HelloSerializer
# This is just a random ViewSet, what is
# important is the custom view below
#action(detail=False)
def test(self, request):
return Response(request.user.is_anonymous)
Here the result shows that the user not logged in:
True
So the first view shows that request.user.is_anonymous = False and the second shows that request.user.is_anonymous = True. Both views are in the same file views.py in the same app.
What do I miss here? We are not supposed to get the user instance in an API REST?
I suppose this is because your first view is pure Django and it's not using DRF's DEFAULT_AUTHENTICATION_CLASSES. To enable it, you can add #api_view decorator:
from rest_framework.decorators import api_view
from rest_framework.response import Response
#api_view()
def index(request):
return Response("is_anonymous: %s" % request.user.is_anonymous)
Also you should update DEFAULT_AUTHENTICATION_CLASSES to enable OAuth, like this:
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'rest_framework_social_oauth2.authentication.SocialAuthentication',
),
}
As neverwalkaloner mentioned in the in the comments, the problem was that I didn't pass any access_token in the header via Authorization: Bearer <token>, so of course the server wasn't able to identify the "logged" user that was making the request. Using curl (or Postman) I could add this token for checking purpose and it worked.

CSRF is only checked when authenticated in DRF?

TLDR; It seems that my POSTs (to DRF endpoints) are only CSRF protected, if the client has an authenticated session. This is wrong, and leaves the application option to login CSRF attacks. How can I fix this?
I'm starting to build a django rest framework API for a ReactJS frontend, and we want everything, including the authentication, to be handled via API. We are using SessionAuthentication.
If I have an authenticated session, then CSRF works entirely as expected (when auth'd the client should have a CSRF cookie set, and this needs to be paired with the csrfmiddlewaretoken in the POST data).
However, when not authenticated, no POSTs seem to be subject to CSRF checks. Including the (basic) login APIView that has been created. This leaves the site vulnerable to login CSRF exploits.
Does anyone know how to enforce CSRF checks even on unathenticated sessions? and/or how DRF seems to bypass CSRF checks for login?
Below is my rough setup ...
settings.py:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
views.py:
class Login(APIView):
permission_classes = (permissions.AllowAny,)
#method_decorator(csrf_protect) # shouldn't be needed
def post(self, request, format=None):
user = authenticate(
request,
username=request.POST['username'],
password=request.POST['password']
)
# ... perform login logic ...
def get(self, request, format=None):
"""
Client must GET the login to obtain CSRF token
"""
# Force generation of CSRF token so that it's set in the client
get_token(request)
return Response(None)
urls.py:
urlpatterns = [
url(r'^login/$', views.Login.as_view(), name='login'),
]
expected behaviour:
login_url = reverse('login')
login_details = {
'username': self.user.email,
'password': self.password,
}
client = APIClient(enforce_csrf_checks=True)
# Try to just POST to a CSRF protected view with no CSRF
response = client.post(reverse('login'), login_details)
# response status should be 403 Missing or incorrect CSRF
# GET the login API first to obtain CSRF
client.get(reverse('login'))
login_details['csrfmiddlewaretoken'] = client.cookies.get('csrftoken').value
# Now POST to the login API with the CSRF cookie and CSRF token in the POST data
response = client.post(reverse('login'), login_details)
# response status should now be 200 (and a newly rotated CSRF token delivered)
actual behaviour:
client = APIClient(enforce_csrf_checks=True)
# Try to just to a CSRF protected view with no CSRF
response = client.post(reverse('login'), login_details)
# BROKEN: response status is 200, client is now logged in
# Post to the exact same view again, still with no CSRF
response = client.post(reverse('login'), login_details)
# response status is now 403
# BROKEN: This prooves that this view is protected against CSRF, but ONLY for authenticated sessions.
Django REST Framework is disabling CSRF token requirement when using SessionAuthentication and user is not authenticated. This is by design to not mess up other authentication method that don't require CSRF authentication (because they're not based on cookies) and you should ensure by yourself that CSRF is validated on login request and it is mentioned in last paragraph of SessionAuthentication documentation. It is advised to either use non-API login process or ensure that API-based login process is fully protected.
You can check how DRFs SessionAuthentication is enforcing CSRF validation when you are logged in and base your view on that.
You can create a child class of APIView that forces CSRF.
from rest_framework import views
class ForceCRSFAPIView(views.APIView):
#classmethod
def as_view(cls, **initkwargs):
# Force enables CSRF protection. This is needed for unauthenticated API endpoints
# because DjangoRestFramework relies on SessionAuthentication for CSRF validation
view = super().as_view(**initkwargs)
view.csrf_exempt = False
return view
Then all you need to do is change your login view to descend from this
class Login(ForceCRSFAPIView)
# ...