I'm trying to write a custom authentication middleware for Django channels since I'm using JWT for my app's authentication scheme. I'm using the method that is mentioned in this article which basically gets user's token in the first request that is made to the websockets and then, in the receive method of the consumers.py file, fetches user's data based on that and then pours it in the self.scope['user'] (can't make use of the token_auth.py method because the UI app is separate..). Now since I have NO ACCESS to the request param that is usually being used in the views.py files to get the user's data, is there anyway to get the user's data out of an access token alone??
Hello #Jalal try this.
from channels.auth import AuthMiddlewareStack
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from rest_framework_simplejwt.backends import TokenBackend
class TokenAuthMiddleware:
"""
Token authorization middleware for Django Channels 2
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
headers = dict(scope['headers'])
if b'authorization' in headers:
try:
token_name, token_key = headers[b'authorization'].decode().split()
if token_name == 'Token':
print(token_key)
valid_data = TokenBackend(algorithm='HS256').decode(token_key,verify=False)
print(valid_data)
scope['user_id'] = valid_data['user_id']
close_old_connections()
except Token.DoesNotExist:
scope['user'] = AnonymousUser()
return self.inner(scope)
TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))
Here valid_data return this:
{'token_type': 'access', 'exp': 1627994463, 'jti':
'192275bd7fd649758838ave1899e1863', 'user_id': 2}
If you want to fetch more data(like:Username,email,etc) through access token then use Customizing token claims:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token['username'] = user.username
token['email'] = user.email
# ...
return token
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
I am trying to test that my custom permissions in DRF work properly, so I need to imitate some requests made by users with different rights (the rights are stored as attributes in the db in a table linked to the default User table).
I am using RequestFactory for setting the request user, but it doesn't seem to work properly, as it still sees the request user as AnonymousUser, and returns AttributeError when checking the permission.
Here's an example of my testcase:
class TestSingleClient(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.user = User.objects.create(
username='admin',
password='admin',
first_name='Name',
last_name='Surname',
email='user#gmail.com',
is_staff=True
)
AccountProfile.objects.create(
phone='8999999999',
occupation='tst_occupation',
user=self.user
)
Client.objects.create(
name='Client1',
type=0)
def test_get_single_client(self):
client = Client.objects.get(name='Client1')
request = self.factory.get('/system/clients/{}/'.format(client.id))
request.user = self.user
response = ClientView.as_view()(request, id=client.id)
client_data = Client.objects.get(id=client.id)
serializer = ClientSerializer(client_data)
self.assertEqual(response.data, serializer.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
which results in:
File "***/core/extra.py", line 10, in my_permissions_func
roles = AccessRoles.objects.filter(user_groups=user.user_profile.group_id, can_read=True).\
AttributeError: 'AnonymousUser' object has no attribute 'user_profile'
Any suggestions for fixing that?
P.S. A similar topic was discussed here: Django RequestFactory doesn't store a User, which didn't bring me any closer to the answer.
I recently added django-axes to my Django project. It is suppose to work out the box with django-restframework. However, I am using django-rest-framework-simplejwt to handle authentication. But it should still work out the box since the only thing that is required for django-axes is passing Django's authentication method the request object which it does in it's source code (line 39 and 43).
When I try to authenticate, I get this error from django-axes:
axes.exceptions.AxesBackendRequestParameterRequired: AxesBackend requires a request as an argument to authenticate
You need to add requests to the authentication function. See sample code below.
serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
def _authenticate_user_email(self, email, password, request):
# This is key: Pass request to the authenticate function
self.user = authenticate(email=email, password=password, request=request)
return self.user
def validate(self, attrs):
password = attrs.get('password')
email = attrs.get('email')
request = self.context.get('request') # <<=== Grab request
self.user = self._authenticate_user_email(email, password, request)
# All error handling should be done by this code line
refresh = self.get_token(self.user)
data = {}
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
return data
views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from authy.serializers import MyTokenObtainPairSerializer
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
urls.py
from authy.views import MyTokenObtainPairView
url(r'^/auth/api/token/$', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
It is also worth mentioning that the simple jwt lib uses the authenticate function, however it does not pass the request parameter. Therefore you need call authenticate, get_token and return data object yourself.
In addition, if you have extended the AbstractBaseUser model of django. And set the USERNAME_FIELD. Then use the param username instead of email. E.g: authenticate(username=email, password=password, request=request)
Use this:
from axes.backends import AxesBackend
class MyBackend(AxesBackend)
def authenticate(self, request=None, *args, **kwargs):
if request:
return super().authenticate(request, *args, **kwargs)
This would skip the AxesBackend in AUTHENTICATION_BACKENDS if the request is unset and would weaken your security setup.
source: https://github.com/jazzband/django-axes/issues/478
I'm using Django JWT authentication with the Django Rest Framework.
How can I get user info of the logged in user after I retrieve the token?
just check your app settings file, whether you have specified the jwt authentication backend or not.
if it mentioned there and if you are using User model ( in otherwords django.contrib.auth.models.User) request.user will work
If you are using your own custom User model
from django.conf import settings
from rest_framework import authentication
from rest_framework import exceptions
from rest_framework.authentication import get_authorization_header
import CustomUser # just import your model here
import jwt
class JWTAuthentication(authentication.BaseAuthentication):
def authenticate(self, request): # it will return user object
try:
token = get_authorization_header(request).decode('utf-8')
if token is None or token == "null" or token.strip() == "":
raise exceptions.AuthenticationFailed('Authorization Header or Token is missing on Request Headers')
print(token)
decoded = jwt.decode(token, settings.SECRET_KEY)
username = decoded['username']
user_obj = CustomUser.objects.get(username=username)
except jwt.ExpiredSignature :
raise exceptions.AuthenticationFailed('Token Expired, Please Login')
except jwt.DecodeError :
raise exceptions.AuthenticationFailed('Token Modified by thirdparty')
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed('Invalid Token')
except Exception as e:
raise exceptions.AuthenticationFailed(e)
return (user_obj, None)
def get_user(self, userid):
try:
return CustomUser.objects.get(pk=userid)
except Exception as e:
return None
and add the following settings in your app
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'path_to_custom_authentication_backend',
....
)
}
now in each view/viewset you can access the user object with request.user
By reading the documentation on DRF Authentication and as #neverwalkaloner mentions in his comment, we see that we can access the logged-in user's django.contrib.auth.User instance in a view, by using the request.user attribute.
Reading the documentations of both the recommended JWT modules for DRF:
https://github.com/GetBlimp/django-rest-framework-jwt
https://github.com/davesque/django-rest-framework-simplejwt
I didn't find any evidence that they change/override the method of accesing the logged in user's instance info.
If you are familiar with django rest jwt, you may see a config like this in your settings.py:
JWT_AUTH = {
.....
'JWT_RESPONSE_PAYLOAD_HANDLER':
'rest_framework_jwt.utils.jwt_response_payload_handler',
'JWT_SECRET_KEY': SECRET_KEY,
....
}
You can simply create a method for example my_custom_jwt_response_payload_handler like below and address JWT_RESPONSE_PAYLOAD_HANDLER to new handler:
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token': token,
'user': {
'username': user.username, 'id': user.id,
...
}
}
You can add any data that you want in this response. then patch settings.py with your new handler:
JWT_AUTH = {
.....
'JWT_RESPONSE_PAYLOAD_HANDLER':
'localtion-to-my-own-handler-file.my_custom_jwt_response_payload_handler',
....
}
For better understanding i suggest read original source and comments for jwt_response_payload_handler in here
Once you logged in, means you are authenticated by Django, now you can retrieve current user details anywhere in the code.
request.user
request contains all the details of the User Model once the user gets authenticated. otherwise, it will show the Anonymus user.
I'm building a RESTful API with Django and django-rest-framework.
As authentication mechanism we have chosen "Token Authentication" and I have already implemented it following Django-REST-Framework's documentation, 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?
What is the best practice?
Anybody here experienced with Django REST Framework and could suggest a technical solution?
(the last question has lower priority)
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'),
)
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',
),
}
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"
}
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)
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.
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?
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)
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.
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)
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
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