127๐
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_header
from rest_framework.exceptions import AuthenticationFailed
class 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'),
)
34๐
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 datetime
from django.utils.timezone import utc
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from django.http import HttpResponse
import json
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.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, url
from weights import views
urlpatterns = 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 datetime
from django.utils.timezone import utc
from rest_framework.authentication import TokenAuthentication
from rest_framework import exceptions
class 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',
),
}
- [Django]-How to delete project in django
- [Django]-Naming convention for Django URL, templates, models and views
- [Django]-How do you change the collation type for a MySQL column?
7๐
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"
}
- [Django]-How to produce a 303 Http Response in Django?
- [Django]-Define css class in django Forms
- [Django]-Django โ iterate number in for loop of a template
6๐
Iโve tried @odedfos answer but I had misleading error. Here is the same answer, fixed and with proper imports.
views.py
from django.utils import timezone
from rest_framework import status
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.views import ObtainAuthToken
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.object['user'])
if not created:
# update the created time of the token to keep it valid
token.created = datetime.datetime.utcnow().replace(tzinfo=utc)
token.save()
return Response({'token': token.key})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
authentication.py
from datetime import timedelta
from django.conf import settings
from django.utils import timezone
from rest_framework.authentication import TokenAuthentication
from rest_framework import exceptions
EXPIRE_HOURS = getattr(settings, 'REST_FRAMEWORK_TOKEN_EXPIRE_HOURS', 24)
class 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')
if token.created < timezone.now() - timedelta(hours=EXPIRE_HOURS):
raise exceptions.AuthenticationFailed('Token has expired')
return (token.user, token)
- [Django]-Django rest framework serializing many to many field
- [Django]-Setting DEBUG = False causes 500 Error
- [Django]-How does the get_or_create function in Django return two values?
6๐
The author asked
the question is, should the application renew / change the Token periodically and if yes how? Should it be the mobile app that requires the token to be renewed or the web-app should do it autonomously?
But all of the answers are writing about how to automatically change the token.
I think change token periodically by token is meaningless. The rest framework create a token that has 40 characters, if the attacker tests 1000 token every second, it requires 16**40/1000/3600/24/365=4.6*10^7
years to get the token. You should not worried that the attacker will test your token one by one. Even you changed your token, the probability of guess you token is the same.
If you are worried that maybe the attackers can get you token, so you change it periodically, than after the attacker get the token, he can also change you token, than the real user is kicked out.
What you should really do is to prevent tha attacker from getting your userโs token, use https.
By the way, Iโm just saying change token by token is meaningless, change token by username and password is sometimes meanful. Maybe the token is used in some http environment (you should always avoid this kind of situation) or some third party (in this case, you should create different kind of token, use oauth2) and when the user is doing some dangerous thing like changing binding mailbox or delete account, you should make sure you will not use the origin token anymore because it may has been revealed by the attacker using sniffer or tcpdump tools.
- [Django]-Django 1.5b1: executing django-admin.py causes "No module named settings" error
- [Django]-Django REST Framework : "This field is required." with required=False and unique_together
- [Django]-Is it secure to store passwords as environment variables (rather than as plain text) in config files?
4๐
You can leverage http://getblimp.github.io/django-rest-framework-jwt
This library is able generate token that has an expiration date
To understand the difference between DRF default token and the token provide by the DRF take a look at:
How to make Django REST JWT Authentication scale with mulitple webservers?
- [Django]-Retrieving a Foreign Key value with django-rest-framework serializers
- [Django]-Django Rest Framework โ Updating a foreign key
- [Django]-Embedding JSON objects in script tags
2๐
Itโs a good practice to set an expiration mechanism on your app whether for mobile client or web client. There are two common solutions:
-
system expires token (after specific time) and user has to login again to gain new valid token.
-
system automatically expires old token (after specific time) and replaces it with new one (change token).
Common things in both of solutions:
Changes in settings.py
DEFAULT_AUTHENTICATION_CLASSES = [
# you replace right path of 'ExpiringTokenAuthentication' class
'accounts.token_utils.ExpiringTokenAuthentication'
]
TOKEN_EXPIRED_AFTER_MINUTES = 300
Create token_utils.py
from django.conf import settings
from datetime import timedelta
from django.conf import settings
from django.utils import timezone
from rest_framework.authentication import TokenAuthentication
from rest_framework.authtoken.models import Token
from rest_framework.exceptions import AuthenticationFailed
def expires_in(token: Token):
elapsed_time = timezone.now() - token.created
return timedelta(minutes=settings.TOKEN_EXPIRED_AFTER_MINUTES) - elapsed_time
def is_token_expired(token):
return expires_in(token) < timedelta(seconds=0)
Changes in your views:
@api_view(['GET'])
@authentication_classes([ExpiringTokenAuthentication])
@permission_classes([IsAuthenticated])
def test(request):
...
return Response(response, stat_code)
If using option 1, add these lines to token_utils.py
def handle_token_expired(token):
Token.objects.filter(key=token).delete()
class ExpiringTokenAuthentication(TokenAuthentication):
def authenticate_credentials(self, key):
try:
token = Token.objects.get(key=key)
except Token.DoesNotExist:
raise AuthenticationFailed("Invalid Token!")
if not token.user.is_active:
raise AuthenticationFailed("User inactive or deleted")
if is_token_expired(token):
handle_token_expired(token)
msg = "The token is expired!, user have to login again."
response = {"msg": msg}
raise AuthenticationFailed(response)
return token.user, token
If using option 2, add these lines to token_utils.py
def handle_token_expired(token):
is_expired = is_token_expired(token)
if is_expired:
token.delete()
token = Token.objects.create(user = token.user)
return is_expired, token
class ExpiringTokenAuthentication(TokenAuthentication):
"""
when token is expired, it will be removed
and new one will be created
"""
def authenticate_credentials(self, key):
try:
token = Token.objects.get(key = key)
except Token.DoesNotExist:
raise AuthenticationFailed("Invalid Token")
if not token.user.is_active:
raise AuthenticationFailed("User is not active")
is_expired, token = handle_token_expired(token)
if is_expired:
raise AuthenticationFailed("The Token is expired")
return (token.user, token)
- [Django]-Django can' t load Module 'debug_toolbar': No module named 'debug_toolbar'
- [Django]-Backwards migration with Django South
- [Django]-How can I call a custom Django manage.py command directly from a test driver?
0๐
If you notice that a token is like a session cookie then you could stick to the default lifetime of session cookies in Django: https://docs.djangoproject.com/en/1.4/ref/settings/#session-cookie-age.
I donโt know if Django Rest Framework handles that automatically but you can always write a short script which filters out the outdated ones and marks them as expired.
- [Django]-Django models.py Circular Foreign Key
- [Django]-Include intermediary (through model) in responses in Django Rest Framework
- [Django]-Altering one query parameter in a url (Django)
0๐
Just thought I would add mine as this was helpful for me. I usually go with the JWT method but sometimes something like this is better. I updated the accepted answer for django 2.1 with proper imports..
authentication.py
from datetime import timedelta
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
from rest_framework.authentication import TokenAuthentication
from rest_framework import exceptions
EXPIRE_HOURS = getattr(settings, 'REST_FRAMEWORK_TOKEN_EXPIRE_HOURS', 24)
class ExpiringTokenAuthentication(TokenAuthentication):
def authenticate_credentials(self, key):
try:
token = self.get_model().objects.get(key=key)
except ObjectDoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token')
if not token.user.is_active:
raise exceptions.AuthenticationFailed('User inactive or deleted')
if token.created < timezone.now() - timedelta(hours=EXPIRE_HOURS):
raise exceptions.AuthenticationFailed('Token has expired')
return token.user, token
views.py
import datetime
from pytz import utc
from rest_framework import status
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.serializers import AuthTokenSerializer
class ObtainExpiringAuthToken(ObtainAuthToken):
def post(self, request, **kwargs):
serializer = AuthTokenSerializer(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().replace(tzinfo=utc)
token.save()
return Response({'token': token.key})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- [Django]-Django models: Only permit one entry in a model?
- [Django]-Equivalent of PHP "echo something; exit();" with Python/Django?
- [Django]-Django datetime issues (default=datetime.now())
0๐
just to keep adding to @odedfos answer, I think there have been some changes to the syntax so the code of ExpiringTokenAuthentication needs some adjusting:
from rest_framework.authentication import TokenAuthentication
from datetime import timedelta
from datetime import datetime
import datetime as dtime
import pytz
class ExpiringTokenAuthentication(TokenAuthentication):
def authenticate_credentials(self, key):
model = self.get_model()
try:
token = model.objects.get(key=key)
except 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.now(dtime.timezone.utc)
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
Also, donโt forget to add it to DEFAULT_AUTHENTICATION_CLASSES instead of rest_framework.authentication.TokenAuthentication
- [Django]-Differences between STATICFILES_DIR, STATIC_ROOT and MEDIA_ROOT
- [Django]-Django: Error: You don't have permission to access that port
- [Django]-Name '_' is not defined
0๐
If anyone wants to expire the token after certain time of inactivity, below answer would help. I am tweaking one of the answers given here. I have added comments to the code I added
from rest_framework.authentication import TokenAuthentication
from datetime import timedelta
from datetime import datetime
import datetime as dtime
import pytz
class ExpiringTokenAuthentication(TokenAuthentication):
def authenticate_credentials(self, key):
model = self.get_model()
try:
token = model.objects.get(key=key)
except 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.now(dtime.timezone.utc)
utc_now = utc_now.replace(tzinfo=pytz.utc)
if token.created < utc_now - timedelta(minutes=15): # TOKEN WILL EXPIRE AFTER 15 MINUTES OF INACTIVITY
token.delete() # ADDED THIS LINE SO THAT EXPIRED TOKEN IS DELETED
raise exceptions.AuthenticationFailed('Token has expired')
else:
token.created = utc_now #THIS WILL SET THE token.created TO CURRENT TIME WITH EVERY REQUEST
token.save() #SAVE THE TOKEN
return token.user, token
- [Django]-Can't install via pip because of egg_info error
- [Django]-Is it better to use path() or url() in urls.py for django 2.0?
- [Django]-How do I use pagination with Django class based generic ListViews?
0๐
According to DRFโs documentaion:
For an implementation which allows more than one token per user, has some tighter security implementation details, and supports token expiry, please see the Django REST Knox third party package
(https://www.django-rest-framework.org/api-guide/authentication/)
- [Django]-Where is a good place to work on accounts/profile in Django with the Django registration app?
- [Django]-Django model blank=False does not work?
- [Django]-Python 3 list(dictionary.keys()) raises error. What am I doing wrong?