Skip to content

Instantly share code, notes, and snippets.

@AndrewPix
Last active January 3, 2024 14:23
Show Gist options
  • Star 33 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save AndrewPix/cdd9276b1d5683459b965d5cc4517b26 to your computer and use it in GitHub Desktop.
Save AndrewPix/cdd9276b1d5683459b965d5cc4517b26 to your computer and use it in GitHub Desktop.
Integrate django-rest-knox with django-rest-auth
from rest_framework import serializers
from rest_auth.serializers import UserDetailsSerializer
class KnoxSerializer(serializers.Serializer):
"""
Serializer for Knox authentication.
"""
token = serializers.CharField()
user = UserDetailsSerializer()
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication',),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
}
REST_AUTH_TOKEN_MODEL = 'knox.models.AuthToken'
REST_AUTH_TOKEN_CREATOR = 'project.apps.accounts.utils.create_knox_token'
REST_AUTH_SERIALIZERS = {
'USER_DETAILS_SERIALIZER': 'project.apps.accounts.serializers.UserDetailsSerializer',
'TOKEN_SERIALIZER': 'project.apps.accounts.serializers.KnoxSerializer',
}
from knox.models import AuthToken
def create_knox_token(token_model, user, serializer):
token = AuthToken.objects.create(user=user)
return token
from rest_framework.response import Response
from rest_auth.views import LoginView
from rest_auth.registration.views import RegisterView
from allauth.account.utils import complete_signup
from allauth.account import app_settings as allauth_settings
from .serializers import KnoxSerializer
from .utils import create_knox_token
class KnoxLoginView(LoginView):
def get_response(self):
serializer_class = self.get_response_serializer()
data = {
'user': self.user,
'token': self.token
}
serializer = serializer_class(instance=data, context={'request': self.request})
return Response(serializer.data, status=200)
class KnoxRegisterView(RegisterView):
def get_response_data(self, user):
return KnoxSerializer({'user': user, 'token': self.token}).data
def perform_create(self, serializer):
user = serializer.save(self.request)
self.token = create_knox_token(None, user, None)
complete_signup(self.request._request, user, allauth_settings.EMAIL_VERIFICATION, None)
return user
@bobozar
Copy link

bobozar commented May 25, 2019

Thanks for this man! You rock.

@allynt
Copy link

allynt commented Nov 27, 2019

This is brilliant! Thanks.

One question, though, why are you serializing the token as a string representation of the (instance, token) tuple returned by create_knox_token? Doesn't the client just need the token itself? I've rewritten it like this:

class KnoxSerializer(serializers.Serializer):
    """
    Serializer for Knox authentication.
    """
    token = serializers.SerializerMethodField()
    user = UserDetailsSerializer()

   def get_token(self, ,obj):
      return obj["token"][1]

@AndrewPix
Copy link
Author

@allynt Thanks! I am not sure, it seems like that code worked for me at the time of creation. I will check this out.

@shitikamiyako
Copy link

shitikamiyako commented Aug 27, 2020

This is brilliant! Thanks.

One question,

I'm using dj-rest-auth, an extension of django-rest-auth, and I've tried to integrate django-rest-knox with it in your code.

2020-08-28_04h40_14

I then POST the API to try and register with the URL http://127.0.0.1:8000/dj-rest-auth/registration/ and the result was that the token was stored in the AuthToken model and at the same time the User model (which is not the Django standard but, CustomUserModel. ) has also saved the user's record .

However, an

"AttributeError at /dj-rest-auth/registration/
'CustomUser' object has no attribute 'auth_token'" 

error.

I thought it was because the User model does not have an auth_token field, so I tried to migrate it again.

But as a result I get the error
"users.CustomUser.auth_token: (models.E006) The field 'auth_token' clashes with the field 'auth_token' "
from model 'users.customuser'.

I can't figure out why I'm getting an

"AttributeError at /dj-rest-auth/registration/
'CustomUser' object has no attribute 'auth_token'"

errorr .

Could you please advise me on how to resolve this?
I can attach the code such as models.py separately if needed.

I'm Japanese, I'm not very good at English and I don't have much programming experience.
Feel free to tell me if there is something difficult to understand in your question.

@deepak1235
Copy link

deepak1235 commented Dec 8, 2020

This is brilliant! Thanks.

One question,

I'm using dj-rest-auth, an extension of django-rest-auth, and I've tried to integrate django-rest-knox with it in your code.

2020-08-28_04h40_14

I then POST the API to try and register with the URL http://127.0.0.1:8000/dj-rest-auth/registration/ and the result was that the token was stored in the AuthToken model and at the same time the User model (which is not the Django standard but, CustomUserModel. ) has also saved the user's record .

However, an

"AttributeError at /dj-rest-auth/registration/
'CustomUser' object has no attribute 'auth_token'" 

error.

I thought it was because the User model does not have an auth_token field, so I tried to migrate it again.

But as a result I get the error
"users.CustomUser.auth_token: (models.E006) The field 'auth_token' clashes with the field 'auth_token' "
from model 'users.customuser'.

I can't figure out why I'm getting an

"AttributeError at /dj-rest-auth/registration/
'CustomUser' object has no attribute 'auth_token'"

errorr .

Could you please advise me on how to resolve this?
I can attach the code such as models.py separately if needed.

I'm Japanese, I'm not very good at English and I don't have much programming experience.
Feel free to tell me if there is something difficult to understand in your question.

make sure that you have the correct urlpattern order and that you have added the KnoxLoginView in the urlpattern. I changed the urlpattern to the following:

urlpatterns = [
    path('login/', KnoxLoginView.as_view(), name='knox_login'),
    path('knox/', include('knox.urls')), #for logout
    path('registration/', KnoxRegisterView.as_view(), name='knox_register'),
    path('', include('dj_rest_auth.urls')),
    path('facebook/', FacebookLogin.as_view(), name='fb_login')
]

Hope this helps!

P.S.: make sure that you change your code to what it was when you got the attribute error..

@mnoah66
Copy link

mnoah66 commented Aug 6, 2022

Love this and it's working fine except for one thing. After logging in, the expiry date is not included in the response.

urls.py

path('api/v1/', include("api.urls")),
path('api/v1/login/', KnoxLoginView.as_view(), name='knox_login'),
path('api-auth/', include('rest_framework.urls')),
path('api/v1/dj-rest-auth/', include('dj_rest_auth.urls')),

views.py

from dj_rest_auth.views import LoginView

class KnoxLoginView(LoginView):
    
    def get_response(self):
        serializer_class = self.get_response_serializer()
        print(self)
        data = {
            'user': self.user,
            'token': self.token
        }
        serializer = serializer_class(instance=data, context={'request': self.request})

        return Response(serializer.data, status=200)

settings.py

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
    ),
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication',),
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}

REST_AUTH_TOKEN_MODEL = 'knox.models.AuthToken'
REST_AUTH_TOKEN_CREATOR = 'api.utils.create_knox_token'

REST_AUTH_SERIALIZERS = {
    'USER_DETAILS_SERIALIZER': 'api.serializers.UserDetailsSerializer',
    'TOKEN_SERIALIZER': 'api.serializers.KnoxSerializer',
}
from rest_framework.settings import api_settings
REST_KNOX = {
  'TOKEN_TTL': timedelta(minutes=1),
  'TOKEN_LIMIT_PER_USER': None,
  'AUTO_REFRESH': False,
  'EXPIRY_DATETIME_FORMAT': api_settings.DATETIME_FORMAT
}

api.utils

from knox.models import AuthToken

def create_knox_token(token_model, user, serializer):
    token = AuthToken.objects.create(user=user)
    return token

@BhuwanPandey
Copy link

BhuwanPandey commented Aug 22, 2022

This is brilliant! Thanks.

One question,

I'm using dj-rest-auth, an extension of django-rest-auth, and I've tried to integrate django-rest-knox with it in your code.

2020-08-28_04h40_14

I then POST the API to try and register with the URL http://127.0.0.1:8000/dj-rest-auth/registration/ and the result was that the token was stored in the AuthToken model and at the same time the User model (which is not the Django standard but, CustomUserModel. ) has also saved the user's record .

However, an

"AttributeError at /dj-rest-auth/registration/
'CustomUser' object has no attribute 'auth_token'" 

error.

I thought it was because the User model does not have an auth_token field, so I tried to migrate it again.

But as a result I get the error "users.CustomUser.auth_token: (models.E006) The field 'auth_token' clashes with the field 'auth_token' " from model 'users.customuser'.

I can't figure out why I'm getting an

"AttributeError at /dj-rest-auth/registration/
'CustomUser' object has no attribute 'auth_token'"

errorr .

Could you please advise me on how to resolve this? I can attach the code such as models.py separately if needed.

I'm Japanese, I'm not very good at English and I don't have much programming experience. Feel free to tell me if there is something difficult to understand in your question.

Main reason behind this is that,
When you are using dj_rest_auth registration method
In RegisterView there is method called get_response_data(self, user) which
return TokenSerializer(user.auth_token, context=self.get_serializer_context()).data

Here user.auth_token is main cause of errors raises
when you look on AuthToken in knox.models ,you found
user = models.ForeignKey(User, null=False, blank=False,
related_name='auth_token_set', on_delete=models.CASCADE)
whose related_name is auth_token_set not auth_token

Solution for this is,
1.Try to use custom register view as mention above
or change the auth_token to auth_token_set by overriding the
get_response_data(self, user) method of dj_rest_auth.registration.views.registerview

Hope you got it..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment