Django-Rest-Framework system to check custom HTTP header (application - token) - django

I use Django and Django- rest-framework.
I have to check a custom http header for a lot of my views.
For each view I need to:
Check if the http custom header is there (X-APP-TOKEN);
Check if this token is correct;
Serve the request or return an HTTP error (for example 403);
Is there some approach that I can follow?
For example something like permissions_class for rest-framework view.
I tried to implement a custom permission like this:
class IsAuthorizedApplication(BasePermission):
def has_permission(self, request, view):
app_id = request.META.get(app_settings.APPS_HEADER_AUTHORIZATION_APP_ID)
secret_token = request.META.get(app_settings.APPS_HEADER_AUTHORIZATION_APP_TOKEN)
if app_id and secret_token:
try:
selected_app = Application.objects.get(app_uuid=app_id, status=ApplicationStatusType.ACTIVE)
// Check secret token
return True
except Application.DoesNotExist:
return False
return False
But I think that this approach is based to the authentication system of djnago-rest-framework. Infact in case of 'false return' I receive:
401 - {"detail":"Authentication credentials were not provided."}
Is there some different approach to check custom http headers like the permission-class or have I to write a base View to check the application-token before to serve the request?

you can use this
https://pypi.org/project/djangorestframework-api-key/
Install the latest version with pip:
pip install djangorestframework-api-key
# settings.py
INSTALLED_APPS = [
# ...
"rest_framework",
"rest_framework_api_key",
]
be sure that "rest_framework_api_key", cames after "rest_framework",
Run the included migrations:
python manage.py migrate
then from admin create new key
now
HasAPIKey permission class protects a view behind API key authorization.
You can set the permission globally:
# settings.py
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework_api_key.permissions.HasAPIKey",
]
}
or on a per-view basis:
# views.py
from rest_framework.views import APIView
from rest_framework_api_key.permissions import HasAPIKey
class UserListView(APIView):
permission_classes = [HasAPIKey]
# ...
Authorization header
By default, clients must pass their API key via the Authorization header. It must be formatted as follows:
Authorization: Api-Key ********
where ******** refers to the generated API key.
or you can do this
For example, if you set:
# settings.py
API_KEY_CUSTOM_HEADER = "X-APP-TOKEN"
then clients must make authorized requests using:
X-APP-TOKEN: ********

it seems like this response is return by the django auth brcause you have pass auth token in header, for the permission above code looks fine. It should return the 403.
please check the the default setting of the djnago restframework
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
If not specified, this setting defaults to allowing unrestricted access:
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]

in order to implement custom authentication you need to inherit from "BaseAuthentication" and override the authenticate method it must return None for non-authenticated users or (user, auth) for authenticated users refer to docs for more info
https://www.django-rest-framework.org/api-guide/authentication/#custom-authentication

Related

I have configured Django REST Framework API Key, I want to expose one API to call without API key in Authorization header, What configuration needed?

This is the code block I have added to my settings.py file. Is there any way to expose an API that can be called without an authorization header?
I don't want to use the JWT token. There is no user interface in the application its an integrator project.
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework_api_key.permissions.HasAPIKey",
]
}
If you use AllowAny permission, then you will not need any authorization, authorization header and token etc. API will be public/open to all.
Add AllowAny in the permission_classes attribute of the view.
from rest_framework import permissions
class ExampleView(generics.RetrieveAPIView):
permission_classes = [permissions.AllowAny]
Note:
It does not have to be a generics.***APIView. It works with APIView too. Also for the function-based views, you can use decorators.
#api_view(['GET'])
#permission_classes([permissions.AllowAny])
def example_view(request, format=None):
...
Reference: https://www.django-rest-framework.org/api-guide/permissions/#allowany

csrf_exempt set but CSRF Failed: Referer checking failed - no Referer

I have a backend API, it's in django and deployed on Google Endpoint.
I have a post request that insert data to my DB.
I created a script to use this endpoint but I got this error:
{"detail":"CSRF Failed: Referer checking failed - no Referer."}
Regarding over posts I added the crsf_exempt decorator to my class but it did not change.
I try to add the decorator two ways:
class AddUser(APIView):
""" Create user and company from csv """
#method_decorator(csrf_exempt)
def post(self, request):
#method_decorator(csrf_exempt, name='dispatch')
class AddUser(APIView):
""" Create user and company from csv """
def post(self, request):
But both failed.
This is how I contact my endpoint:
resp = requests.request(
method, url,
headers={'Authorization': 'Bearer {}'.format(
open_id_connect_token)}, **kwargs)
Any ideas ?
Thanks
EDIT
So I tried to add authentication classes to my views but it appears to be a bad idea. This is being real trouble for me.
I tried to get the csrftoken doing like this:
client = requests.session()
# Retrieve the CSRF token first
client.get(url) # sets cookie
print(client.cookies)
if 'csrftoken' in client.cookies:
# Django 1.6 and up
csrftoken = client.cookies['csrftoken']
else:
# older versions
csrftoken = client.cookies
Thing is, I am using IAP to protect my API and I do not have any csrftoken cookie but I do have a something looking like this:
<RequestsCookieJar[<Cookie GCP_IAP_XSRF_NONCE_Q0sNuY-M83380ypJogZscg=1
for ...
How can I use this to make post request to my API ?
So this happened to me because I did not set any authentication_classes to my generic view.
When this option is not set Django automatically use the SessionBackend, which need the csrf token.
I fixed it by adding this to my view: authentication_classes = [ModelBackend, GoogleOAuth2]
#Kimor - Can you try doing this in your urls.py
from django.views.decorators.csrf import csrf_exempt
url('^test/$', csrf_exempt(views.TestView.as_view())),
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 TestView.as_view().
source
Django REST Framework CSRF Failed: CSRF cookie not set
So after working on this project for a while this is what I learned regarding the CSRF using Django.
First of all, if you are using django templates, or in any cases where your back-end and front-end are running behind the same server the most common practice is to use session for authentication.
This is activated by default by DRF.
This means that in your DRF configuration if you do not explicitly set the DEFAULT_AUTHENTICATION_CLASSES option default authentication will be set to Session + BasicAuth.
In this configuration you'll need to manage the CSRF token as described in the documentation (https://docs.djangoproject.com/en/4.0/ref/csrf/).
If your back-end and front-end are separated as in my case, using CSRF is not the only solution or even the recommended one.
As in my case I use JWT behind IAP (Identity Aware Proxy, provided by google). I had to write my own authentication classes and then use it in my DRF conf:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'main.authentication_backend.custom_auth.IAPAuthentication'],
...
}
Here is explain how to write your own authentication class: https://www.django-rest-framework.org/api-guide/authentication/#custom-authentication.

How to use created Django API Key to access API in browser?

In my django app I have created a url that could give Django REST framework API.The url is http://127.0.0.1:8000/api/events/
I have added API Key restrictions in settings.py so that only people who have API Key could access the API service.
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework_api_key.permissions.HasAPIAccess",
]
}
Then in my Django Admin site I created an API Key as below.
Now I want use this API Key to access my API. But I don't know how to use it.
I tried to use some url in my browser like
http://127.0.0.1:8000/api/events/?key=4278348c-2ff3-4f72-99e8-7284832d6049
But it still shows an error page as API Key is missing.
Does anyone know how can I access the API in browser? Thank you.
You can extend Django REST Framework's TokenAuthentication with your own method, and use that instead. Here's how:
my_app/auth.py
from rest_framework.authentication import TokenAuthentication
class TokenAuthGet(TokenAuthentication):
"""
Extends the class to support token as "key" in a GET Query Parameter.
Supports standard method in header as a default.
"""
def authenticate(self, request):
token = request.query_params.get("key", False)
if token and "HTTP_AUTHORIZATION" not in request.META:
return self.authenticate_credentials(token)
else:
return super().authenticate(request)
Then in your setup:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'my_app.auth.TokenAuthGet',
],
}
Then you'll be able to pass the token as ?key= like you give in your example URL. Good luck!

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.