From cc04a50709141c67f7efbb0678f948c7f061be14 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 13 Aug 2019 14:14:18 +0300 Subject: [PATCH] version 0.0.5.12: refactored authorization app (added JWT support) --- apps/account/models.py | 21 ++ apps/authorization/serializers/common.py | 116 ++++++++-- apps/authorization/urls/common.py | 64 +++-- apps/authorization/views/common.py | 283 +++++++++-------------- project/settings/base.py | 40 +++- project/urls/__init__.py | 6 +- requirements/base.txt | 5 +- 7 files changed, 302 insertions(+), 233 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index e5d1b362..df25e50a 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -40,6 +40,16 @@ class UserQuerySet(models.QuerySet): """Filter only active users.""" return self.filter(is_active=switcher) + def by_access_token(self, token): + """Find user by access token""" + return self.filter(oauth2_provider_accesstoken__token=token, + oauth2_provider_accesstoken__expires__gt=timezone.now()) + + def by_refresh_token(self, token): + """Find user by access token""" + return self.filter(oauth2_provider_refreshtoken__token=token, + oauth2_provider_refreshtoken__expires__gt=timezone.now()) + class User(ImageMixin, AbstractUser): """Base user model.""" @@ -62,6 +72,17 @@ class User(ImageMixin, AbstractUser): """String method.""" return "%s:%s" % (self.email, self.get_short_name()) + def get_user_info(self): + """Get information about user""" + return { + "username": self.username, + "first_name": self.first_name if self.first_name else None, + "last_name": self.last_name if self.last_name else None, + "email": self.email if self.email else None, + "newsletter": self.newsletter, + "is_active": self.is_active + } + def change_status(self, switcher: bool = False): """Method to set user status to active or inactive""" self.is_active = switcher diff --git a/apps/authorization/serializers/common.py b/apps/authorization/serializers/common.py index 417352c3..048c1a05 100644 --- a/apps/authorization/serializers/common.py +++ b/apps/authorization/serializers/common.py @@ -2,11 +2,19 @@ from django.contrib.auth import password_validation as password_validators from rest_framework import serializers from rest_framework import validators as rest_validators +from django.contrib.auth import authenticate +from django.conf import settings from account import models as account_models from authorization.models import Application from utils import exceptions as utils_exceptions +# JWT +from rest_framework_simplejwt.tokens import RefreshToken, SlidingToken, UntypedToken + + +JWT_SETTINGS = settings.SIMPLE_JWT + # Mixins class BaseAuthSerializerMixin(serializers.Serializer): @@ -14,6 +22,30 @@ class BaseAuthSerializerMixin(serializers.Serializer): source = serializers.ChoiceField(choices=Application.SOURCES) +class JWTBaseMixin(serializers.Serializer): + """ + Mixin for JWT authentication. + Uses in serializers when need give in response access and refresh token + """ + # RESPONSE + refresh_token = serializers.CharField(read_only=True) + access_token = serializers.CharField(read_only=True) + + def get_token(self): + """Create JWT token""" + user = self.instance + token = RefreshToken.for_user(user) + token['user'] = user.get_user_info() + return token + + def to_representation(self, instance): + """Override to_representation method""" + token = self.get_token() + setattr(instance, 'refresh_token', str(token)) + setattr(instance, 'access_token', str(token.access_token)) + return super().to_representation(instance) + + class LoginSerializerMixin(BaseAuthSerializerMixin): """Mixin for login serializers""" password = serializers.CharField(write_only=True) @@ -26,7 +58,7 @@ class ClassicAuthSerializerMixin(BaseAuthSerializerMixin): # Serializers -class SignupSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer): +class SignupSerializer(JWTBaseMixin, serializers.ModelSerializer): """Signup serializer serializer mixin""" # REQUEST username = serializers.CharField( @@ -35,13 +67,13 @@ class SignupSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer): ) password = serializers.CharField(write_only=True) email = serializers.EmailField(write_only=True) - newsletter = serializers.BooleanField() + newsletter = serializers.BooleanField(write_only=True) class Meta: model = account_models.User fields = ( - 'username', 'first_name', 'last_name', 'password', - 'newsletter', 'email', 'source' + 'username', 'password', 'email', 'newsletter', + 'access_token', 'refresh_token', ) def validate_password(self, data): @@ -64,47 +96,93 @@ class SignupSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer): return obj -class LoginSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer): - """Serializer for login user""" +class LoginByUsernameSerializer(JWTBaseMixin, serializers.ModelSerializer): + """Serializer for login user by username and password""" username = serializers.CharField(write_only=True) password = serializers.CharField(write_only=True) class Meta: """Meta-class""" model = account_models.User - fields = ('username', 'password', 'source') + fields = ( + 'username', 'password', 'refresh_token', 'access_token' + ) + + def validate(self, attrs): + """Override validate method""" + username = attrs.pop('username') + password = attrs.pop('password') + user = authenticate(username=username, + password=password) + if not user: + raise utils_exceptions.UserNotFoundError() + self.instance = user + return attrs -class RefreshTokenSerializer(BaseAuthSerializerMixin): - """Serializer for refresh token view""" - refresh_token = serializers.CharField(write_only=True) - - -class LoginByEmailSerializer(LoginSerializerMixin, serializers.ModelSerializer): - """Serializer for signing up user by email""" +class LoginByEmailSerializer(JWTBaseMixin, serializers.ModelSerializer): + """Serializer for login user""" email = serializers.EmailField(write_only=True) + password = serializers.CharField(write_only=True) class Meta: """Meta-class""" model = account_models.User - fields = ('email', 'password', 'source') + fields = ( + 'email', 'password', 'refresh_token', 'access_token' + ) def validate(self, attrs): """Override validate method""" + email = attrs.pop('email') + password = attrs.pop('password') try: - user = account_models.User.objects.get(email=attrs.get('email')) - attrs['username'] = user.get_username() + user = account_models.User.objects.get(email=email) except account_models.User.DoesNotExist: raise utils_exceptions.UserNotFoundError() else: + user = authenticate(username=user.get_username(), + password=password) + if not user: + raise utils_exceptions.UserNotFoundError() + self.instance = user return attrs -class LogoutSerializer(BaseAuthSerializerMixin): - """Serializer for logout""" +class RefreshTokenSerializer(serializers.Serializer): + """Serializer for refresh token view""" + refresh_token = serializers.CharField() + access_token = serializers.CharField(read_only=True) + + def validate(self, attrs): + """Override validate method""" + token = RefreshToken(attrs['refresh_token']) + + data = {'access_token': str(token.access_token)} + + if JWT_SETTINGS.get('ROTATE_REFRESH_TOKENS'): + if JWT_SETTINGS.get('BLACKLIST_AFTER_ROTATION'): + try: + # Attempt to blacklist the given refresh token + token.blacklist() + except AttributeError: + # If blacklist app not installed, `blacklist` method will + # not be present + pass + + token.set_jti() + token.set_exp() + + data['refresh_token'] = str(token) + + return data # OAuth class OAuth2Serialzier(BaseAuthSerializerMixin): """Serializer OAuth2 authorization""" token = serializers.CharField(max_length=255) + + +class OAuth2LogoutSerializer(BaseAuthSerializerMixin): + """Serializer for logout""" diff --git a/apps/authorization/urls/common.py b/apps/authorization/urls/common.py index d0ac84ce..1b9e1c4c 100644 --- a/apps/authorization/urls/common.py +++ b/apps/authorization/urls/common.py @@ -1,55 +1,51 @@ """Common url routing for application authorization""" from django.conf import settings -from django.conf.urls import url, include from django.urls import path +from django.conf.urls import url from oauth2_provider.views import AuthorizationView +from social_django import views as social_django_views from rest_framework_social_oauth2 import views as drf_social_oauth2_views from social_core.utils import setting_name -from social_django import views as social_django_views from authorization.views import common as views extra = getattr(settings, setting_name('TRAILING_SLASH'), True) and '/' or '' -app_name = 'oauth2' +app_name = 'auth' urlpatterns_social_django = [ - # authentication / association - url(r'^login/(?P[^/]+){0}$'.format(extra), social_django_views.auth, - name='begin'), + url(r'^authorize/?$', AuthorizationView.as_view(), name="authorize"), url(r'^complete/(?P[^/]+){0}$'.format(extra), social_django_views.complete, name='complete'), - # disconnection - url(r'^disconnect/(?P[^/]+){0}$'.format(extra), social_django_views.disconnect, - name='disconnect'), - url(r'^disconnect/(?P[^/]+)/(?P\d+){0}$' - .format(extra), social_django_views.disconnect, name='disconnect_individual'), ] -urlpatterns_rest_framework_social_oauth2 = [ - url(r'^authorize/?$', AuthorizationView.as_view(), name="authorize"), - url('', include('social_django.urls', namespace="social")), - url(r'^invalidate-sessions/?$', drf_social_oauth2_views.invalidate_sessions, - name="invalidate_sessions") -] - -urlpatterns_api = [ - # sign up - path('signup/facebook/', views.SocialSignUpView.as_view(), name='signup-facebook'), - path('signup/', views.SignUpView.as_view(), name='signup'), - # sign in - path('login/username/', views.LoginByUsernameView.as_view(), name='login-username'), - path('login/email/', views.LoginByEmailView.as_view(), name='login-email'), +urlpatterns_oauth2 = [ + path('oauth2/signup/facebook/', views.OAuth2SignUpView.as_view(), + name='oauth2-signup-facebook'), # for admin sign in page - path('token/', drf_social_oauth2_views .TokenView.as_view(), name="token"), - # logout - path('logout/', views.LogoutView.as_view(), name="logout"), - path('revoke-token/', views.RevokeTokenView.as_view(), name="revoke-token"), - # refresh token - path('refresh-token/', views.RefreshTokenView.as_view(), name="refresh-token"), + path('oauth2/token/', drf_social_oauth2_views .TokenView.as_view(), + name="token"), ] -urlpatterns = urlpatterns_api + \ - urlpatterns_social_django + \ - urlpatterns_rest_framework_social_oauth2 +urlpatterns_jwt = [ + path('signup/', views.SignUpView.as_view(), + name='signup'), + # sign in + path('login/username/', views.LoginByUsernameView.as_view(), + name='login-username'), + path('login/email/', views.LoginByEmailView.as_view(), + name='login-email'), + # refresh token + path('refresh-token/', views.RefreshTokenView.as_view(), + name="refresh-token"), + # logout + # path('logout/', views.LogoutView.as_view(), + # name="logout"), +] + + +urlpatterns = urlpatterns_jwt + \ + urlpatterns_oauth2 + \ + urlpatterns_social_django # for social oauth2 + diff --git a/apps/authorization/views/common.py b/apps/authorization/views/common.py index 73e32744..72f24e9f 100644 --- a/apps/authorization/views/common.py +++ b/apps/authorization/views/common.py @@ -7,21 +7,29 @@ from django.utils.translation import gettext_lazy as _ from oauth2_provider.oauth2_backends import OAuthLibCore from oauth2_provider.settings import oauth2_settings from oauth2_provider.views.mixins import OAuthLibMixin +from rest_framework import generics from rest_framework import permissions from rest_framework import status -from rest_framework.generics import GenericAPIView from rest_framework.response import Response from rest_framework_social_oauth2.oauth2_backends import KeepRequestCore from rest_framework_social_oauth2.oauth2_endpoints import SocialTokenServer +from rest_framework_simplejwt import tokens as jwt_tokens +from rest_framework.settings import settings as rest_settings +from django.utils import timezone +from rest_framework_simplejwt.utils import datetime_to_epoch +from account.models import User from authorization.models import Application from authorization.serializers import common as serializers from utils import exceptions as utils_exceptions -from utils import permissions as utils_permissions + + +# JWT # Mixins -class BaseViewMixin(GenericAPIView): +# OAuth2 +class BaseOAuth2ViewMixin(generics.GenericAPIView): """BaseMixin for classic auth views""" def get_client_id(self, source) -> str: """Get application client id""" @@ -43,7 +51,7 @@ class BaseViewMixin(GenericAPIView): 'detail': _('Not found an application with this source')}) -class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseViewMixin): +class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseOAuth2ViewMixin): """Basic mixin for OAuth2 views""" server_class = oauth2_settings.OAUTH2_SERVER_CLASS validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS @@ -68,8 +76,31 @@ class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseViewMixin): raise utils_exceptions.ServiceError() -# Sign in -class SocialSignUpView(OAuth2ViewMixin, GenericAPIView): +# JWT +# Login base view mixin +class JWTViewMixin(generics.GenericAPIView): + """JWT view mixin""" + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + response = Response(serializer.data, status=status.HTTP_200_OK) + if 'locale' in request.COOKIES: + # Write locale in cookie + key, value = 'locale', request.COOKIES.get('locale') + response.set_cookie(key=key, value=value) + # Write to cookie access and refresh token with secure flag + response.set_cookie(key='access_token', + value=serializer.data.get('access_token'), + secure=True) + response.set_cookie(key='refresh_token', + value=serializer.data.get('refresh_token'), + secure=True) + return response + + +# Serializers +# Sign in via Facebook +class OAuth2SignUpView(OAuth2ViewMixin, generics.GenericAPIView): """ Implements an endpoint to convert a provider token to an access token @@ -83,6 +114,18 @@ class SocialSignUpView(OAuth2ViewMixin, GenericAPIView): permission_classes = (permissions.AllowAny, ) serializer_class = serializers.OAuth2Serialzier + def get_jwt_token(self, user: User, + access_token: str, + refresh_token: str): + """Get JWT token""" + token = jwt_tokens.RefreshToken.for_user(user) + # Adding additional information about user to payload + token['user'] = user.get_user_info() + # Adding OAuth2 tokens to payloads + token['oauth2_fb'] = {'access_token': access_token, + 'refresh_token': refresh_token} + return token + def post(self, request, *args, **kwargs): """Override POST method""" # Preparing request data @@ -99,191 +142,93 @@ class SocialSignUpView(OAuth2ViewMixin, GenericAPIView): request._request.POST[key] = value url, headers, body, oauth2_status = self.create_token_response(request._request) - response = Response(data=json.loads(body), status=oauth2_status) - - for k, v in headers.items(): - response[k] = v - return response + body = json.loads(body) + # Get JWT token + if oauth2_status != status.HTTP_200_OK: + raise ValueError('status isn\'t 200') + user = User.objects.by_access_token(token=body.get('access_token'))\ + .first() + token = self.get_jwt_token(user=user, + access_token=body.get('access_token'), + refresh_token=body.get('refresh_token')) + return Response(data={'refresh': str(token), + 'access': str(token.access_token)}, + status=status.HTTP_200_OK) -class SignUpView(OAuth2ViewMixin, GenericAPIView): +# JWT +# Sign in via username and password +class SignUpView(JWTViewMixin): """View for classic signup""" permission_classes = (permissions.AllowAny, ) serializer_class = serializers.SignupSerializer - def post(self, request): - """Post-method to sign up new user""" + def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() - request_data = self.prepare_request_data(serializer.validated_data) - request_data.update({ - 'grant_type': 'password', - 'username': serializer.validated_data.get('username'), - 'password': serializer.validated_data.get('password'), - }) - # Use the rest framework `.data` to fake the post body of the django request. - request._request.POST = request._request.POST.copy() - for key, value in request_data.items(): - request._request.POST[key] = value - - url, headers, body, oauth2_status = self.create_token_response(request._request) - response = Response(data=json.loads(body), status=oauth2_status) - - for k, v in headers.items(): - response[k] = v + response = Response(serializer.data, status=status.HTTP_201_CREATED) + if 'locale' in request.COOKIES: + # Write locale in cookie + key, value = 'locale', request.COOKIES.get('locale') + response.set_cookie(key=key, value=value) + # Write to cookie access and refresh token with secure flag + response.set_cookie(key='access_token', + value=serializer.data.get('access_token'), + secure=True) + response.set_cookie(key='refresh_token', + value=serializer.data.get('refresh_token'), + secure=True) return response -# Login -class LoginByUsernameView(OAuth2ViewMixin, GenericAPIView): - """ - Implements an endpoint to provide access tokens - - The endpoint is used in the following flows: - - * Authorization code - * Password - * Client credentials - """ +# Login by username + password +class LoginByUsernameView(JWTViewMixin): + """Login by username""" permission_classes = (permissions.AllowAny,) - serializer_class = serializers.LoginSerializer - - def post(self, request, *args, **kwargs): - # Preparing request data - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - request_data = self.prepare_request_data(serializer.validated_data) - request_data.update({ - 'grant_type': 'password', - 'username': serializer.validated_data.get('username'), - 'password': serializer.validated_data.get('password'), - }) - # Use the rest framework `.data` to fake the post body of the django request. - request._request.POST = request._request.POST.copy() - for key, value in request_data.items(): - request._request.POST[key] = value - - url, headers, body, oauth2_status = self.create_token_response(request._request) - response = Response(data=json.loads(body), status=oauth2_status) - - for k, v in headers.items(): - response[k] = v - return response + serializer_class = serializers.LoginByUsernameSerializer -class LoginByEmailView(LoginByUsernameView): - """ - Implements an endpoint to provide access tokens - - The endpoint is used in the following flows: - - * Authorization code - * Password - * Client credentials - """ +# Login by email + password +class LoginByEmailView(JWTViewMixin): + """Login by email and password""" + permission_classes = (permissions.AllowAny,) serializer_class = serializers.LoginByEmailSerializer -# Logout -class LogoutView(GenericAPIView): - """Logout view""" - permission_classes = (permissions.IsAuthenticated, ) - serializer_class = serializers.LogoutSerializer - - def post(self, request, *args, **kwargs): - """Method to logout user by deleting access tokens by source""" - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - # Setup attributes - source = serializer.validated_data.get('source') - request.user.revoke_refresh_tokens(source=source) - return Response(status=status.HTTP_204_NO_CONTENT) - - -class RevokeTokenView(OAuth2ViewMixin, GenericAPIView): - """ - Implements an endpoint to revoke access or refresh tokens - """ - permission_classes = (permissions.AllowAny,) - serializer_class = serializers.OAuth2Serialzier - - def post(self, request, *args, **kwargs): - # Use the rest framework `.data` to fake the post body of the django request. - # Preparing request data - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - request_data = self.prepare_request_data(serializer.validated_data) - # Use the rest framework `.data` to fake the post body of the django request. - request._request.POST = request._request.POST.copy() - for key, value in request_data.items(): - request._request.POST[key] = value - - url, headers, body, oauth2_status = self.create_revocation_response(request._request) - response = Response(data=json.loads(body) if body else '', - status=oauth2_status if body else status.HTTP_204_NO_CONTENT) - - for k, v in headers.items(): - response[k] = v - return response - - -class RefreshTokenView(OAuth2ViewMixin, GenericAPIView): - """ - Implements an endpoint to provide access tokens - - The endpoint is used in the following flows: - * Authorization code - * Password - * Client credentials - """ - permission_classes = (utils_permissions.IsAuthenticatedAndHasRefreshToken, ) +# Refresh access_token +class RefreshTokenView(generics.GenericAPIView): + """Refresh access_token""" + permission_classes = (permissions.IsAuthenticated,) serializer_class = serializers.RefreshTokenSerializer def post(self, request, *args, **kwargs): - # Preparing request data + """POST method""" serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - request_data = self.prepare_request_data(serializer.validated_data) - request_data.update({ - 'grant_type': 'refresh_token', - 'refresh_token': serializer.validated_data.get('refresh_token') - }) - # Use the rest framework `.data` to fake the post body of the django request. - request._request.POST = request._request.POST.copy() - for key, value in request_data.items(): - request._request.POST[key] = value - - url, headers, body, oauth2_status = self.create_token_response(request._request) - response = Response(data=json.loads(body), status=oauth2_status) - - for k, v in headers.items(): - response[k] = v + response = Response(serializer.validated_data, status=status.HTTP_200_OK) + if 'locale' in request.COOKIES: + # Write locale in cookie + key, value = 'locale', request.COOKIES.get('locale') + response.set_cookie(key=key, value=value) + # Write to cookie access and refresh token with secure flag + response.set_cookie(key='access_token', + value=serializer.data.get('access_token'), + secure=True) + response.set_cookie(key='refresh_token', + value=serializer.data.get('refresh_token'), + secure=True) return response +# Logout +# class LogoutView(generics.GenericAPIView): +# """Logout user""" +# permission_classes = (permissions.IsAuthenticated,) +# +# def post(self, request, *args, **kwargs): +# """POST method""" +# current_datetime = timezone.now() +# token = request.headers.get('Authorization').split(' ')[::-1][0] +# access_token = jwt_tokens.AccessToken(token) +# access_token.lifetime = timezone.timedelta(seconds=1) +# return Response(status=status.HTTP_200_OK) - -# Utils -class TokenView(CsrfExemptMixin, OAuthLibMixin, GenericAPIView): - """ - Implements an endpoint to provide access tokens - - The endpoint is used in the following flows: - - * Authorization code - * Password - * Client credentials - """ - permission_classes = (permissions.AllowAny,) - - def post(self, request, *args, **kwargs): - # Use the rest framework `.data` to fake the post body of the django request. - request._request.POST = request._request.POST.copy() - for key, value in request.data.items(): - request._request.POST[key] = value - - url, headers, body, oauth2_status = self.create_token_response(request._request) - response = Response(data=json.loads(body), status=oauth2_status) - - for k, v in headers.items(): - response[k] = v - return response diff --git a/project/settings/base.py b/project/settings/base.py index dd01b90d..e9e354e8 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/2.2/ref/settings/ import os import sys +from datetime import timedelta # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -67,7 +68,8 @@ EXTERNAL_APPS = [ 'oauth2_provider', 'social_django', 'rest_framework_social_oauth2', - 'django_extensions' + 'django_extensions', + 'rest_framework_simplejwt.token_blacklist' ] @@ -197,10 +199,8 @@ REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', - # OAuth - 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', - 'rest_framework_social_oauth2.authentication.SocialAuthentication', - + # JWT + 'rest_framework_simplejwt.authentication.JWTAuthentication', ), 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning', 'DEFAULT_VERSION': (AVAILABLE_VERSIONS['current'],), @@ -236,8 +236,8 @@ AUTHENTICATION_BACKENDS = ( # } # Override default OAuth2 namespace -DRFSO2_URL_NAMESPACE = 'oauth2' -SOCIAL_AUTH_URL_NAMESPACE = 'oauth2' +DRFSO2_URL_NAMESPACE = 'auth' +SOCIAL_AUTH_URL_NAMESPACE = 'auth' OAUTH2_SOCIAL_AUTH_BACKEND_NAME = 'facebook' OAUTH2_SOCIAL_AUTH_GRANT_TYPE = 'convert_token' OAUTH2_PROVIDER_APPLICATION_MODEL = 'authorization.Application' @@ -328,3 +328,29 @@ RESETTING_TOKEN_EXPIRATION = 24 # hours # CORS Config CORS_ORIGIN_ALLOW_ALL = True CORS_ALLOW_CREDENTIALS = False + + +# JWT +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), + 'ROTATE_REFRESH_TOKENS': True, + 'BLACKLIST_AFTER_ROTATION': True, + + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': SECRET_KEY, + 'VERIFYING_KEY': None, + + 'AUTH_HEADER_TYPES': ('Bearer',), + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', + + 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken', ), + 'TOKEN_TYPE_CLAIM': 'token_type', + + 'JTI_CLAIM': 'jti', + + 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', + 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), + 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), +} diff --git a/project/urls/__init__.py b/project/urls/__init__.py index 223a88cb..ee6aa906 100644 --- a/project/urls/__init__.py +++ b/project/urls/__init__.py @@ -51,8 +51,8 @@ urlpatterns_doc = [ ] -urlpatterns_social = [ - path('api/oauth2/', include('authorization.urls.common')), +urlpatterns_auth = [ + path('api/auth/', include('authorization.urls.common')), ] urlpatterns = [ @@ -61,7 +61,7 @@ urlpatterns = [ ] urlpatterns = urlpatterns + \ - urlpatterns_social + \ + urlpatterns_auth + \ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/requirements/base.txt b/requirements/base.txt index 702708e5..3530e62f 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -24,4 +24,7 @@ django-rest-framework-social-oauth2==1.1.0 django-extensions==2.2.1 # CORS -django-cors-headers==3.0.2 \ No newline at end of file +django-cors-headers==3.0.2 + +# JWT +djangorestframework_simplejwt==4.3.0 \ No newline at end of file