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

Asked By: F.D.F.

||

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
Answered By: F.D.F.

@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.

Answered By: Charlesthk

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'),
]
Answered By: dtatarkin

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")
Answered By: Sapnesh Naik

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
Answered By: minsu kim

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)
Answered By: Ilya Petukhov

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,
...,
}
Answered By: Faizan Ahmad

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):
        ...
Answered By: Eduardo Tolmasquim