DRF - JWT How to fix Token still active after expired? - django

I'm using Djangorestframework with djangorestframework-simplejwt library, the token system is working except that after an access and refresh token are both expired ( I can confirm with postman ) the frontent app (Vue & axios) is able to still GET the updated data, how is this possible? When i check the axios request the token is the same as the one I use in postman, in Postman it gives me "Token Invalid or expired" but in axios it receives all the data and 200 OK.
These are the configs:
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(hours=24),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'AUTH_HEADER_TYPES': ('JWT',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
}
urls.py
from rest_framework_simplejwt.views import TokenRefreshView
from dgmon.views import MyTokenObtainPairView
app_name = 'dgmon'
admin.site.site_header = settings.ADMIN_SITE_HEADER
admin.site.site_title = settings.ADMIN_SITE_TITLE
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^', include('dgmon.urls')),
path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from dgmon.serializers import MyTokenObtainPairSerializer
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
data = super().validate(attrs)
refresh = self.get_token(self.user)
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
data['user'] = self.user.username
data['groups'] = self.user.groups.values_list('name', flat=True)
return data

Related

TestCase for Loginview using ModelViewSet or GenericViewset

I am trying to write a testcase for my login in django rest framework.
I tried browsing through net where I tried with APIClient, django-Client, Factory but didn't get the result.
I getting the following response:
{'non_field_errors': [ErrorDetail(string='Unable to log in with provided credentials.', code='authorization')]}
even after supply the correct credentials
Here is my test case file:
"""
Test cases for Login
"""
import json
from django.urls import reverse
from django.test import TestCase
from rest_framework.test import APIClient
class LoginTest(TestCase):
"""
Login test cases
"""
def setUp(self):
"""
Setup data for the login test cases
"""
self.valid_payload = json.dumps(
{"username": "admin#ksbsgroup.com", "password": "dell#123"}
)
self.url = reverse("users:login-list")
def test_valid_login(self):
"""
Test login with a valid login
"""
client = APIClient()
response = client.post(
self.url, data=self.valid_payload, content_type="application/json"
)
print(response.data)
self.assertEqual(response.status_code, 200)
My Login view is as follows:
"""
Login view
"""
import logging
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from common import messages
log = logging.getLogger(__name__)
class LoginViewSet(ObtainAuthToken, viewsets.GenericViewSet):
"""
Login view set for login
"""
def create(self, request):
"""
Login the user with the specified email and password.
parameters:
--------------------
email(str): Email address to login
password(str): Password of the user
returns:
--------------------
dict: json dictionary
"""
serializer = self.serializer_class(
data=request.data, context={"request": request}
)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data.get("user")
token, _ = Token.objects.get_or_create(user=user)
log.info(messages.LOG_USER_LOGIN.format(user))
return Response(
{
"message": messages.INFO_SUCCESS,
"token": token.key,
"user": user.id,
"email": user.email,
"status": status.HTTP_200_OK,
}
)
my app url file:
"""
Url paths for Users application
"""
from rest_framework.routers import DefaultRouter
from .views.login import LoginViewSet
router = DefaultRouter()
router.register("login", LoginViewSet, basename="login")
urlpatterns = [] + router.urls
and my project urls file:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("users/", include(("users.urls", "users"), namespace="users")),
]

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.

Django Rest Framework unit testing for PUT request

"test_put_method_success" is showing AssertionError: 404 != 200. How to solve it? ......................
class BasicTest(APITestCase):
def setUp(self):
self.client = Client()
self.user = User(username="admin", email="admin#gmail.com")
self.user.is_staff = True
self.user.set_password('admin')
self.user.save()
def test_put_method_success(self):
url = "http://127.0.0.1:8000/settings/modules/1/"
data = {
'modulename': "Module test update",
'activation_status': "Active"
}
self.assertTrue(self.client.login(username="admin", password="admin"))
response = self.client.put(url, data, format='json')
print(response.status_code)
self.assertEqual(response.status_code, status.HTTP_200_OK)
urls.py
from rest_framework import routers
router = routers.DefaultRouter()
router.register('modules', views.ModuleView)
urlpatterns = [
path('', include(router.urls)),
]
By default DRF PUT does not create an instance.
You need some extra steps as explained by the documentation.

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)

Django Rest Auth - Key error on Email Confirmation

I am trying to setup email verification in DRF using rest-auth.
The registration works correctly and the verification email is sent. However, when going to the verification link I receive a key error.
What I understand is that means that this verification key doesn't exist, but I don't understand how to fix that given that the registration process was supposedly a success?
I have the following paths in my urls.py:
path('', include('rest_framework.urls', namespace='rest_framework')),
path('', include('rest_auth.urls')),
path('registration/', include('rest_auth.registration.urls')),
path('registration/', RegisterView.as_view(), name='account_signup'),
re_path(r'^account-confirm-email/', VerifyEmailView.as_view(), name='account_email_verification_sent'),
re_path(r'^account-confirm-email/(?P<key>[-:\w]+)/$', VerifyEmailView.as_view(), name='account_confirm_email'),
The following settings in my settings.py:
ACCOUNT_AUTHENTICATION_METHOD = 'email'
LOGIN_REDIRECT_URL = '/'
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_CONFIRM_EMAIL_ON_GET = False
ACCOUNT_EMAIL_REQUIRED = True
And this is a screenshot of the error I am getting:
Key error
how I solved this issue
I had to create a view to verify the email my self, also note that I have a custom user model which is the goal when working on a big project
views.py
from rest_auth.registration.views import RegisterView
from django.contrib.auth import get_user_model
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from rest_framework.exceptions import NotFound
from rest_framework.views import APIView
from rest_framework.permissions import AllowAny
from allauth.account.models import EmailConfirmation, EmailConfirmationHMAC
from django.http import HttpResponseRedirect
class ConfirmEmailView(APIView):
permission_classes = [AllowAny]
def get(self, *args, **kwargs):
self.object = confirmation = self.get_object()
confirmation.confirm(self.request)
# A React Router Route will handle the failure scenario
return HttpResponseRedirect('/api/rest-auth/login/')
def get_object(self, queryset=None):
key = self.kwargs['key']
email_confirmation = EmailConfirmationHMAC.from_key(key)
if not email_confirmation:
if queryset is None:
queryset = self.get_queryset()
try:
email_confirmation = queryset.get(key=key.lower())
except EmailConfirmation.DoesNotExist:
# A React Router Route will handle the failure scenario
return HttpResponseRedirect('/login/failure/')
return email_confirmation
def get_queryset(self):
qs = EmailConfirmation.objects.all_valid()
qs = qs.select_related("email_address__user")
return qs
urls.py
from django.contrib import admin
from django.urls import path, re_path
from django.conf.urls import url, include
from rest_auth.registration.views import VerifyEmailView, RegisterView
from rest_auth.views import PasswordResetView, PasswordResetConfirmView
from users.api.views import ConfirmEmailView
urlpatterns = [
path('admin/', admin.site.urls),
url('api/rest-auth/', include('rest_auth.urls')),
url('api/account/', include('users.api.urls')),
url('api/rest-auth/registration/', include('rest_auth.registration.urls')),
url(r'^verify-email/$', VerifyEmailView.as_view(), name='account_email_verification_sent'),
url(r'^rest-auth/registration/account-confirm-email/(?P<key>[-:\w]+)/$', ConfirmEmailView.as_view(), name='account_confirm_email'),
url(r'^rest-auth/password/reset/$', PasswordResetView.as_view(), name='password_reset'),
url(r'^rest-auth/password/reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
]
settings.py
INSTALLED_APPS = [
...
'django.contrib.sites',
'rest_framework',
'rest_framework.authtoken',
'rest_auth',
'rest_auth.registration',
'allauth',
'allauth.account',
'users',
]
SITE_ID = 1
# to use old_password when setting a new password
OLD_PASSWORD_FIELD_ENABLED = True
# to keep the user logged in after password change
LOGOUT_ON_PASSWORD_CHANGE = False
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_LOGOUT_ON_GET = True
# UNSURE
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 5
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 86400 # 1 day in seconds
ACCOUNT_LOGOUT_REDIRECT_URL ='/accounts/login/'
LOGIN_REDIRECT_URL = '/accounts/profile'
SOCIALACCOUNT_EMAIL_VERIFICATION = 'none'
# EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'youremail#gmail.com'
EMAIL_HOST_PASSWORD = 'yourpassword'
DEFAULT_FROM_EMAIL = 'youremail#gmail.com'
DEFAULT_TO_EMAIL = EMAIL_HOST_USER
EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = '/'
NOTE: I noticed that the URLs have to be in that order to work for me when they weren't in that order it didn't work for me. I also noticed that reset passwords give issues too so the fix is also there. I hope this solves your problem. and in case you post a reply and I haven't responded, just mail me at 'opeyemiodedeyi#gmail.com'
This might be an old post but I just wanna share what I used as a solution in hopes that it helps someone else experiencing a similar issue.
# import the confirm_email views from allauth.accounts.views
from allauth.account.views import confirm_email
# once that's done, change your url view portion from
# VerifyEmailView.as_view() to the newly imported view
re_path(r"^account-confirm-email/(?P<key>[-:\w]+)/$", confirm_email,
name="account_confirm_email"),