Token Authentication for RESTful API: should the token be periodically changed? Token Authentication for RESTful API: should the token be periodically changed? django django

Token Authentication for RESTful API: should the token be periodically changed?


It is good practice to have mobile clients periodically renew their authentication token. This of course is up to the server to enforce.

The default TokenAuthentication class does not support this, however you can extend it to achieve this functionality.

For example:

from rest_framework.authentication import TokenAuthentication, get_authorization_headerfrom rest_framework.exceptions import AuthenticationFailedclass ExpiringTokenAuthentication(TokenAuthentication):    def authenticate_credentials(self, key):        try:            token = self.model.objects.get(key=key)        except self.model.DoesNotExist:            raise exceptions.AuthenticationFailed('Invalid token')        if not token.user.is_active:            raise exceptions.AuthenticationFailed('User inactive or deleted')        # This is required for the time comparison        utc_now = datetime.utcnow()        utc_now = utc_now.replace(tzinfo=pytz.utc)        if token.created < utc_now - timedelta(hours=24):            raise exceptions.AuthenticationFailed('Token has expired')        return token.user, token

It is also required to override the default rest framework login view, so that the token is refreshed whenever a login is done:

class ObtainExpiringAuthToken(ObtainAuthToken):    def post(self, request):        serializer = self.serializer_class(data=request.data)        if serializer.is_valid():            token, created =  Token.objects.get_or_create(user=serializer.validated_data['user'])            if not created:                # update the created time of the token to keep it valid                token.created = datetime.datetime.utcnow()                token.save()            return Response({'token': token.key})        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)obtain_expiring_auth_token = ObtainExpiringAuthToken.as_view()

And don't forget to modify the urls:

urlpatterns += patterns(    '',    url(r'^users/login/?$', '<path_to_file>.obtain_expiring_auth_token'),)


If someone is interested by that solution but wants to have a token that is valid for a certain time then gets replaced by a new token here's the complete solution (Django 1.6):

yourmodule/views.py:

import datetimefrom django.utils.timezone import utcfrom rest_framework.authtoken.views import ObtainAuthTokenfrom rest_framework.authtoken.models import Tokenfrom django.http import HttpResponseimport jsonclass ObtainExpiringAuthToken(ObtainAuthToken):    def post(self, request):        serializer = self.serializer_class(data=request.DATA)        if serializer.is_valid():            token, created =  Token.objects.get_or_create(user=serializer.object['user'])            utc_now = datetime.datetime.utcnow()                if not created and token.created < utc_now - datetime.timedelta(hours=24):                token.delete()                token = Token.objects.create(user=serializer.object['user'])                token.created = datetime.datetime.utcnow()                token.save()            #return Response({'token': token.key})            response_data = {'token': token.key}            return HttpResponse(json.dumps(response_data), content_type="application/json")        return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)obtain_expiring_auth_token = ObtainExpiringAuthToken.as_view()

yourmodule/urls.py:

from django.conf.urls import patterns, include, urlfrom weights import viewsurlpatterns = patterns('',    url(r'^token/', 'yourmodule.views.obtain_expiring_auth_token'))

your project urls.py (in the urlpatterns array):

url(r'^', include('yourmodule.urls')),

yourmodule/authentication.py:

import datetimefrom django.utils.timezone import utcfrom rest_framework.authentication import TokenAuthenticationfrom rest_framework import exceptionsclass ExpiringTokenAuthentication(TokenAuthentication):    def authenticate_credentials(self, key):        try:            token = self.model.objects.get(key=key)        except self.model.DoesNotExist:            raise exceptions.AuthenticationFailed('Invalid token')        if not token.user.is_active:            raise exceptions.AuthenticationFailed('User inactive or deleted')        utc_now = datetime.datetime.utcnow()        if token.created < utc_now - datetime.timedelta(hours=24):            raise exceptions.AuthenticationFailed('Token has expired')        return (token.user, token)

In your REST_FRAMEWORK settings add ExpiringTokenAuthentication as an Authentification class instead of TokenAuthentication:

REST_FRAMEWORK = {    'DEFAULT_AUTHENTICATION_CLASSES': (        'rest_framework.authentication.SessionAuthentication',        #'rest_framework.authentication.TokenAuthentication',        'yourmodule.authentication.ExpiringTokenAuthentication',    ),}


Thought I'd give a Django 2.0 answer using DRY. Somebody already built this out for us, google Django OAuth ToolKit. Available with pip, pip install django-oauth-toolkit. Instructions on adding the token ViewSets with routers: https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html. It's similar to the official tutorial.

So basically OAuth1.0 was more yesterday's security which is what TokenAuthentication is. To get fancy expiring tokens, OAuth2.0 is all the rage these days. You get an AccessToken, RefreshToken, and scope variable to fine tune the permissions. You end up with creds like this:

{    "access_token": "<your_access_token>",    "token_type": "Bearer",    "expires_in": 3600,    "refresh_token": "<your_refresh_token>",    "scope": "read"}