Integrating Django REST Framework and Django OAuth Toolkit Authentication - django

I have followed through the documentation on how to integrate both the REST framework and OAuth Toolkit, but when I run a request with a token I receive:
{"detail":"Authentication credentials were not provided."}
Here are my REST settings:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',),
'PAGE_SIZE': 20
}
An example view:
from django.contrib.auth.models import Group
from rest_framework import permissions, viewsets
from oauth2_provider.contrib.rest_framework import TokenHasScope, OAuth2Authentication
from data_commons_security.security.models import Permission, Profile, Role, User
from data_commons_security.security.serializers import (
GroupSerializer,
)
class GroupViewSet(viewsets.ReadOnlyModelViewSet):
authentication_classes = [OAuth2Authentication]
queryset = Group.objects.all()
serializer_class = GroupSerializer
permission_classes = [TokenHasScope]
required_scopes = ['read', 'groups']
And I do have 'oauth2_provider' in my INSTALLED_APPS. I'm able to request and receive tokens without issue. It's only when I use them that I get the above error.

Related

How to use both simple jwt token authentication and BasicAuthentication?

I have an DRF api and I have implemented the simplejwt authentication system. It works well. It is usefull when I want to connect my api from external script (I don't need to store credential and just use the token).
However I also want to be able to use the DRF interface login when i reach my api from browser so I have implemented also the Basic and SessionAuthentication. Is it the good way to do that ?
in my settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
}
in my api views.py
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.decorators import permission_classes, authentication_classes
# Create your views here.
#api_view(['GET'])
##authentication_classes([SessionAuthentication, BasicAuthentication])
#permission_classes([IsAuthenticated])
def get_all(request):
# as a token is used, the user with this token is know in the requets
user = request.user
# show only mesures of user having the token provided
mesures = Mesure.objects.filter(user_id=user.id)
serializer = MesureSerializer(mesures, many=True)
return Response(serializer.data)
In my urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('mesures/', views.get_all),
path('mesure-add/', views.add_mesure),
path('token/', TokenObtainPairView.as_view(), name='obtain_tokens'),
path('token/refresh/', TokenRefreshView.as_view(), name='refresh_token'),
path('api-auth/', include('rest_framework.urls'))
]
As you can see I had to comment the #authentication_classes decorator to make it work for both with token and login. Do you believe this is a good way to proceed ?
You should be fine with this because as per the DRF documentation -
Because we now have a set of permissions on the API, we need to authenticate our requests to it if we want to edit any snippets. We haven't set up any authentication classes, so the defaults are currently applied, which are SessionAuthentication and BasicAuthentication.
Source: Authenticating with the API
Ref: Line 109: rest_framework/views.py and Line 40: rest_framework/settings.py

Django Rest: Why is the access denied, although AccessAny is set as permission?

I want to give all people, without any permissions access to my API. The following definitions I made in my files:
views.py
from rest_framework.views import APIView
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.parsers import JSONParser
from rest_framework.permissions import AllowAny
from django.http import HttpResponse, JsonResponse
from rest_framework import status
from api.models import Application, Indice, Satellite, Band
from api.serializers import OsdSerializer
import logging
logger = logging.getLogger(__name__)
class OsdView(APIView):
permission_classes = [AllowAny]
def get(self, request):
applications = Application.objects.all()
serializer = OsdSerializer(applications, many=True)
return Response({"Applications:": serializer.data})
class DetailView(APIView):
permission_classes = [AllowAny]
def get(self, request, machine_name):
application = Application.objects.get(machine_name=machine_name)
downloads = OsdSerializer(application, context={"date_from": request.query_params.get("from", None), "date_to": request.query_params.get("to", None), "location": request.query_params.get("location", None), })
return Response(downloads.data)
settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
But when I access the API the result is the following instead of the content:
{"detail":"Invalid username/password."}
You also have to add a Authentication:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
],
}
That's why you are getting the error; and do not worry, anyone will see your API data even without (a GET at least!) login.

using django permissions.IsAuthenticatedOrReadOnly with token authentication

I have this Django API view that I want to allow authorized and unauthorized users access it, I have set Django token-authentication as the default authentication class, however, whenever I try to access the view as unauthenticated user,I get error Unauthorized: which is weird coz am making a get request in the view
my code is here
#api_view(['GET'])
#permission_classes([permissions.IsAuthenticatedOrReadOnly])
def all_Search(request):
print(request.headers)
src = request.GET.get('q')
my settings for rest framework is
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
is there a way to work around this? will appreciate any help, thanks
I've tried to reproduce your error but I failed.
This is my configuration:
settings.py
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework.authtoken'
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
urls.py
urlpatterns = [
path('search/', api.all_search, name="search")
]
api.py
from rest_framework import permissions
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
#api_view(['GET'])
#permission_classes([permissions.IsAuthenticatedOrReadOnly])
def all_Search(request):
print(request.headers)
src = request.GET.get('q')
return Response()
test.py
from rest_framework import status
from rest_framework.test import APILiveServerTestCase
from rest_framework.reverse import reverse
class TestTokenAuthorization(APILiveServerTestCase):
def test_can_search_without_token(self):
url = reverse('search', kwargs={})
response = self.client.get(url, {}, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
and this is the result of the test:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
{'Cookie': '', 'Content-Type': 'application/octet-stream'}
Destroying test database for alias 'default'...
I'm using djangorestframework==3.10.3 and python3.7
As you can see, I didn't authenticate the request (no token is passed) and the headers were printed as expected from the permissions.
Maybe your issue is caused by something else in your code. Try to include more details in your question.
By the way, your all_Search function is missing the return Response()
Okey I just decided to try something and it seams to be working, at least for now. I somehow believed that DEFAULT_AUTHENTICATION_CLASSES was the issue in this case and in deed it was, so I had to just remove the
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
#opt to use
#authentication_classes = [TokenAuthentication, SessionAuthentication]
#in my views that requires authentications
in my settings, this was not all though, but now I could access the view either authorized or not: (having auth token or not). but this was not getting authenticated user by default
so I did this
make a view to get a user based on a given token
from django.contrib.auth.models import AnonymousUser
from rest_framework.authtoken.models import Token
def get_user(token):
try:
token = Token.objects.select_related('user').get(key=token)
return token.user
except:
return AnonymousUser
and get user in my view if token exists in the headers
#api_view(['GET'])
#permission_classes([permissions.IsAuthenticatedOrReadOnly])
def all_Search(request):
auth = request.headers.get('Authorization').split(' ')[1]
key = request.headers.get('Authorization').split(' ')[0]
if key == 'Token' and auth != 'null': #used null coz my frontend sends null if key is not available
user = get_user(auth)
print(user)

Authentication credentials were not provided drf

when i trying to access api i'm getting this error:
"detail": "Authentication credentials were not provided."
i have included this in settings.py:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':(
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES':(
'rest_framework.permissions.IsAuthenticated',
)
}
my app api urls.py:
from django.urls import path,include
from . import views
from rest_framework import routers
router = routers.SimpleRouter()
router.register(r'',views.UserViewSet, 'user_list')
urlpatterns = router.urls
my views.py:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.object.all()
serializer_class = serializers.UserSerializers
serializers.py:
from rest_framework import serializers
from users.models import User
class UserSerializers(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email','password')
my main urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('',include(urls)),
path ('', include(user_urls)),
path('api/',include(api_urls)),
when i running localhost:8000/api i'm getting the error
You can't access the api from the browsers url if you are using TokenAuthentication.
as said by #DarkOrb TokenAuthentication expects a authorization header with token as it's value.
So You must pass token whenever you call the api.
You can test your api using postman.
In above image i have passed token in headers of postman to access my api.
When you call your api from frontend side,pass your token along with the request.
If you just want to use your api in only desktop's browser,in that case you can use SessionAuthentication only.For mobile devices Tokenauthentication must be done.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':(
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES':(
'rest_framework.permissions.IsAuthenticated',
)
}
use this in your settings.py file, what is happening is that rest_framework.authentication.TokenAuthentication expects a authorization header with token as it's value, but you can't send that with your browser, to browse API from browser you must have SessionAuthentication enabled.

Unit Testing Django Rest Framework Authentication at Runtime

I basically want to turn TokenAuthentication on but only for 2 unit tests. The only option I've seen so far is to use #override_settings(...) to replace the REST_FRAMEWORK settings value.
REST_FRAMEWORK_OVERRIDE={
'PAGINATE_BY': 20,
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework_csv.renderers.CSVRenderer',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
}
#override_settings(REST_FRAMEWORK=REST_FRAMEWORK_OVERRIDE)
def test_something(self):
This isn't working. I can print the settings before and after the decorator and see that the values changed but django doesn't seem to be respecting them. It allows all requests sent using the test Client or the DRF APIClient object through without authentication. I'm getting 200 responses when I would expect 401 unauthorized.
If I insert that same dictionary into my test_settings.py file in the config folder everything works as expected. However like I said I only want to turn on authentication for a couple of unit tests, not all of them. My thought is that Django never revisits the settings for DRF after initialization. So even though the setting values are correct they are not used.
Has anyone run into this problem and found a solution? Or workaround?
The following workaround works well for me:
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.authentication import TokenAuthentication
try:
from unittest.mock import patch
except ImportError:
from mock import patch
#patch.object(APIView, 'authentication_classes', new = [TokenAuthentication])
#patch.object(APIView, 'permission_classes', new = [IsAuthenticatedOrReadOnly])
class AuthClientTest(LiveServerTestCase):
# your tests goes here
Just thought I'd mention how I solved this. It's not pretty and if anyone has any suggestions to clean it up they are more than welcome! As I mentioned earlier the problem I'm having is documented here (https://github.com/tomchristie/django-rest-framework/issues/2466), but the fix is not so clear. In addition to reloading the DRF views module I also had to reload the apps views module to get it working.
import os
import json
from django.conf import settings
from django.test.utils import override_settings
from django.utils.six.moves import reload_module
from rest_framework import views as drf_views
from rest_framework.test import force_authenticate, APIRequestFactory, APIClient
from apps.contact import views as cm_views
from django.core.urlresolvers import reverse
from django.test import TestCase
from unittest import mock
REST_FRAMEWORK_OVERRIDE={
'PAGINATE_BY': 20,
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework_csv.renderers.CSVRenderer',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
}
def test_authenticated(self):
with override_settings(REST_FRAMEWORK=REST_FRAMEWORK_OVERRIDE):
# The two lines below will make sure the views have the correct authentication_classes and permission_classes
reload_module(drf_views)
reload_module(cm_views)
from apps.contact.views import AccountView
UserModelGet = mock.Mock(return_value=self.account)
factory = APIRequestFactory()
user = UserModelGet(user='username')
view = AccountView.as_view()
# Test non existent account
path = self.get_account_path("1thiswillneverexist")
request = factory.get(path)
force_authenticate(request, user=user)
response = view(request, account_name=os.path.basename(request.path))
self.assertEquals(response.status_code, 200, "Wrong status code")
self.assertEqual(json.loads(str(response.content, encoding='utf-8')), [], "Content not correct for authenticated account request")
# Reset the views permission_classes and authentication_classes to what they were before this test
reload_module(cm_views)
reload_module(drf_views)
Wow that's annoying.
Here's a generic contextmanager that handles the reloading. Note that you can't import the subobject api_settings directly because DRF doesn't alter it on reload, but rather reassigns the module-level object to a new instance, so we just access it from the module directly when we need it.
from rest_framework import settings as api_conf
#contextmanager
def override_rest_framework_settings(new_settings):
with override_settings(REST_FRAMEWORK=new_settings):
# NOTE: `reload_api_settings` is a signal handler, so we have to pass a
# couple things in to get it working.
api_conf.reload_api_settings(setting="REST_FRAMEWORK", value="")
with mock.patch.multiple(
"rest_framework.views.APIView",
authentication_classes=api_conf.api_settings.DEFAULT_AUTHENTICATION_CLASSES,
):
yield
api_conf.reload_api_settings(setting="REST_FRAMEWORK", value="")
NOTE: If you're changing other aspects of the settings, you may also have to patch the following APIView attributes:
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
metadata_class = api_settings.DEFAULT_METADATA_CLASS
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
settings = api_settings