last_login field is not updated when authenticating using Tokenauthentication in Django Rest Framework
Question:
I’m working in a project which relies in a Django User model and TokenAuthentication under DjangoRestFramework
I was requested to get last login datetime for each user and I’ve realized that this field is not getting updated when I call the authentication REST endpoint.
Is this a known fact? Am I missing something I must do in order to get that field updated each time the token authentication is called?
Thanks
Answers:
Well, at the end I inherited from the REST Framework TokenAuthentication, pointing to it in the urls file
url(r'^api-token-auth/', back_views.TokenAuthenticationView.as_view()),
and the View handles the request and manually calls the update_last_login like this:
from django.contrib.auth.models import update_last_login
class TokenAuthenticationView(ObtainAuthToken):
"""Implementation of ObtainAuthToken with last_login update"""
def post(self, request):
result = super(TokenAuthenticationView, self).post(request)
try:
request_user, data = requests.get_parameters(request)
user = requests.get_user_by_username(data['username'])
update_last_login(None, user)
except Exception as exc:
return None
return result
@F.D.F. answer is great. Here is another way of doing it.
We send user_logged_in
signals that will call update_last_login
:
user_logged_in.send(sender=user.__class__, request=request, user=user)
Here is a working view (based on a custom User model that uses email as USERNAME_FIELD) :
from rest_framework import parsers, renderers
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from rest_framework.views import APIView
from django.contrib.auth.signals import user_logged_in
from emailauth.serializers import AuthTokenSerializer, UserSerializer
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
user_logged_in.send(sender=user.__class__, request=request, user=user)
return Response({'token': token.key, 'user': UserSerializer(user).data})
obtain_auth_token = ObtainAuthToken.as_view()
You can find the full source code here : Api View with last_login updated
Hope this helps.
A cleaner way to do it:
from django.contrib.auth.models import update_last_login
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.views import ObtainAuthToken
class LoginToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
result = super().post(request, *args, **kwargs)
token = Token.objects.get(key=result.data['token'])
update_last_login(None, token.user)
return result
And then setup urls.py:
url(r'^api-token-auth/', views.LoginToken.as_view()),
My answer for Django==2.0.5
, django-rest-framework-social-oauth2==1.1.0
from django.contrib.auth import user_logged_in
from oauth2_provider.models import AccessToken
from rest_framework import status
from rest_framework_social_oauth2.views import TokenView
class MyTokenView(TokenView):
def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
if response.status_code == status.HTTP_200_OK:
token = AccessToken.objects.get(token=response.data['access_token'])
user = token.user
user_logged_in.send(sender=type(user), request=request, user=user)
return response
urls.py:
from django.urls import path
urlpatterns = [
path('token', MyTokenView.as_view(), name='token'),
]
Here is my solution using a ViewSet:
views.py:
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework import viewsets
from django.contrib.auth.models import update_last_login
class LoginViewSet(viewsets.ViewSet):
"""Checks email and password and returns an auth token."""
serializer_class = AuthTokenSerializer
def create(self, request):
"""Use the ObtainAuthToken APIView to validate and create a token."""
##update last_login
try:
user = models.User.objects.get(email = request.data['username'])
update_last_login(None, user)
except:
pass
return ObtainAuthToken().post(request)
Now just add this viewset to the urls.py:
router.register('login', views.LoginViewSet, base_name="login")
This is the newest code for django 3.0.8.
🙂
thx F.D.F.!
from django.contrib.auth.models import update_last_login
from rest_framework.authtoken.views import ObtainAuthToken
from django.contrib.auth import get_user_model
class TokenAuthenticationView(ObtainAuthToken):
"""Implementation of ObtainAuthToken with last_login update"""
def post(self, request):
result = super(TokenAuthenticationView, self).post(request)
currentUserModel = get_user_model()
try:
user = currentUserModel.objects.get(username=request.data['username'])
update_last_login(None, user)
except Exception as exc:
return None
return result
impl via signals
from django.dispatch import receiver
from django.db.models.signals import post_save
from django.contrib.auth import user_logged_in
from oauth2_provider.models import AccessToken
@receiver(post_save, sender=AccessToken)
def post_save_access_token(instance, created, raw, **kwargs):
if not created or raw:
return
user_logged_in.send(sender=instance.user.__class__, user=instance.user)
for anyone using knox to authenticate the user then need to edit api.py file and import user_logged_in like below
from django.contrib.auth.signals import user_logged_in
after that in LoginAPI class in the same api.py file add the below line after _, token = AuthToken.objects.create(user) like below
user_logged_in.send(sender=user.__class__, request=request, user=user)
If you’re using Simple JWT for Django, you can do it pretty much easily by following this link. You just have to add
SIMPLE_JWT = {
...,
'UPDATE_LAST_LOGIN': True,
...,
}
Sometimes you don’t want exactly the login time, since front-end stores the token and use it without logging in again.
You can save the current time in every request that user is authenticated and authorized by subclassing APIView
from rest_framework
from django.contrib.auth.models import update_last_login
from rest_framework.views import APIView
class CustomAPIView(APIView):
def check_permissions(self, request):
super().check_permissions(request)
# here user is authorized (otherwise an exception would have been raised)
update_last_login(None, request.user)
class my_endpoint(CustomAPIView):
permission_classes = [IsAuthenticated]
def get(self, request, company_id=None):
...
I’m working in a project which relies in a Django User model and TokenAuthentication under DjangoRestFramework
I was requested to get last login datetime for each user and I’ve realized that this field is not getting updated when I call the authentication REST endpoint.
Is this a known fact? Am I missing something I must do in order to get that field updated each time the token authentication is called?
Thanks
Well, at the end I inherited from the REST Framework TokenAuthentication, pointing to it in the urls file
url(r'^api-token-auth/', back_views.TokenAuthenticationView.as_view()),
and the View handles the request and manually calls the update_last_login like this:
from django.contrib.auth.models import update_last_login
class TokenAuthenticationView(ObtainAuthToken):
"""Implementation of ObtainAuthToken with last_login update"""
def post(self, request):
result = super(TokenAuthenticationView, self).post(request)
try:
request_user, data = requests.get_parameters(request)
user = requests.get_user_by_username(data['username'])
update_last_login(None, user)
except Exception as exc:
return None
return result
@F.D.F. answer is great. Here is another way of doing it.
We send user_logged_in
signals that will call update_last_login
:
user_logged_in.send(sender=user.__class__, request=request, user=user)
Here is a working view (based on a custom User model that uses email as USERNAME_FIELD) :
from rest_framework import parsers, renderers
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from rest_framework.views import APIView
from django.contrib.auth.signals import user_logged_in
from emailauth.serializers import AuthTokenSerializer, UserSerializer
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
user_logged_in.send(sender=user.__class__, request=request, user=user)
return Response({'token': token.key, 'user': UserSerializer(user).data})
obtain_auth_token = ObtainAuthToken.as_view()
You can find the full source code here : Api View with last_login updated
Hope this helps.
A cleaner way to do it:
from django.contrib.auth.models import update_last_login
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.views import ObtainAuthToken
class LoginToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
result = super().post(request, *args, **kwargs)
token = Token.objects.get(key=result.data['token'])
update_last_login(None, token.user)
return result
And then setup urls.py:
url(r'^api-token-auth/', views.LoginToken.as_view()),
My answer for Django==2.0.5
, django-rest-framework-social-oauth2==1.1.0
from django.contrib.auth import user_logged_in
from oauth2_provider.models import AccessToken
from rest_framework import status
from rest_framework_social_oauth2.views import TokenView
class MyTokenView(TokenView):
def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
if response.status_code == status.HTTP_200_OK:
token = AccessToken.objects.get(token=response.data['access_token'])
user = token.user
user_logged_in.send(sender=type(user), request=request, user=user)
return response
urls.py:
from django.urls import path
urlpatterns = [
path('token', MyTokenView.as_view(), name='token'),
]
Here is my solution using a ViewSet:
views.py:
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework import viewsets
from django.contrib.auth.models import update_last_login
class LoginViewSet(viewsets.ViewSet):
"""Checks email and password and returns an auth token."""
serializer_class = AuthTokenSerializer
def create(self, request):
"""Use the ObtainAuthToken APIView to validate and create a token."""
##update last_login
try:
user = models.User.objects.get(email = request.data['username'])
update_last_login(None, user)
except:
pass
return ObtainAuthToken().post(request)
Now just add this viewset to the urls.py:
router.register('login', views.LoginViewSet, base_name="login")
This is the newest code for django 3.0.8.
🙂
thx F.D.F.!
from django.contrib.auth.models import update_last_login
from rest_framework.authtoken.views import ObtainAuthToken
from django.contrib.auth import get_user_model
class TokenAuthenticationView(ObtainAuthToken):
"""Implementation of ObtainAuthToken with last_login update"""
def post(self, request):
result = super(TokenAuthenticationView, self).post(request)
currentUserModel = get_user_model()
try:
user = currentUserModel.objects.get(username=request.data['username'])
update_last_login(None, user)
except Exception as exc:
return None
return result
impl via signals
from django.dispatch import receiver
from django.db.models.signals import post_save
from django.contrib.auth import user_logged_in
from oauth2_provider.models import AccessToken
@receiver(post_save, sender=AccessToken)
def post_save_access_token(instance, created, raw, **kwargs):
if not created or raw:
return
user_logged_in.send(sender=instance.user.__class__, user=instance.user)
for anyone using knox to authenticate the user then need to edit api.py file and import user_logged_in like below
from django.contrib.auth.signals import user_logged_in
after that in LoginAPI class in the same api.py file add the below line after _, token = AuthToken.objects.create(user) like below
user_logged_in.send(sender=user.__class__, request=request, user=user)
If you’re using Simple JWT for Django, you can do it pretty much easily by following this link. You just have to add
SIMPLE_JWT = {
...,
'UPDATE_LAST_LOGIN': True,
...,
}
Sometimes you don’t want exactly the login time, since front-end stores the token and use it without logging in again.
You can save the current time in every request that user is authenticated and authorized by subclassing APIView
from rest_framework
from django.contrib.auth.models import update_last_login
from rest_framework.views import APIView
class CustomAPIView(APIView):
def check_permissions(self, request):
super().check_permissions(request)
# here user is authorized (otherwise an exception would have been raised)
update_last_login(None, request.user)
class my_endpoint(CustomAPIView):
permission_classes = [IsAuthenticated]
def get(self, request, company_id=None):
...