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.
Related
I'm new in Django rest framework and I'm trying do a unit test using Token for my API, but it kept throwing IntegrityError. I've researched many blogs to find the solution but couldn't find. Please help me solve this. Thanks in advance.
Here is the code that I've tried
from django.contrib.auth.models import User
from rest_framework.test import APITestCase, APIRequestFactory, force_authenticate
from rest_framework.authtoken.models import Token
from myapp.api.views import UserViewSet
class UserTestCase(APITestCase):
def setUp(self):
self.superuser = User.objects.create_user(username='superuser', email='uid.sawyer#gmail.com',
password='superuser',
is_staff=True)
self.factory = APIRequestFactory()
self.token = Token.objects.create(user=self.superuser)
self.token.save()
def test_list(self):
request = self.factory.get('/api/users/')
force_authenticate(request, user=self.superuser, token=self.token)
response = UserViewSet.as_view({'get': 'list'})(request)
self.assertEqual(response.status_code, 200)
unique constraint failed: authtoken_token.user_id
This error is coming because token already exists for this user in database. So when it tries to create new token with same user_id it gives an error
If you look at the admin token already exists for users
This is referenced from https://excellencetechnologies.in/blog/django-rest-api-authentication-part5/:
Do a try-except function:
class UserAuth(APIView):
# Manager User Login and other things
def post(self, request):
# login
user = authenticate(
username=request.data.get("username"),
password=request.data.get("password")
)
if user is not None:
# A backend authenticated the credentials
try:
token = Token.objects.get(user_id=user.id)
except Token.DoesNotExist:
token = Token.objects.create(user=user)
return Response(token.key)
else:
# No backend authenticated the credentials
return Response([], status=status.HTTP_401_UNAUTHORIZED)
Finally, I got it. I didn't notice that I had created Token in signals wherein each User creation Token was created. Thanks #Willem Van Onsem
I'm using httpie to test my custom authentication.
http POST http://127.0.0.1:8000/api-token-auth/ username='username1' password='Password123'
I did create a custom auth object using AbstractUser.
Using TokenAuthentication, I followed the docs and added my custom TokenAuthentication in my REST_FRAMEWORK settings:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'regis.models.CustomAuthentication',
)
}
And added rest_framework.authtoken in my installed apps.
My AUTHENTICATION_BACKEND is as follows:
AUTHENTICATION_BACKENDS = [ 'regis.models.CustomAuthentication' ]
And here is my custom authentication class:
class CustomAuthentication(authentication.TokenAuthentication):
def authenticate(self, request):
username = request.META.get('X_USERNAME')
print(username)
user_model = get_user_model()
if not username:
return None
try:
user = user_model.objects.get(username=username)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')
return (user, None)
urls.py:
urlpatterns += [
url(r'^api-token-auth/', views.obtain_auth_token),
]
I'm pretty much following the DRF docs (http://www.django-rest-framework.org/api-guide/authentication/#custom-authentication). If there's any additional info needed to solve this, please let me know and I'll update. Any help on what I'm missing would be great.
To add: Just out of curiosity, do I need to make a custom authentication system if I have a custom user?
UPDATE:
I just deleted the class above, and just added the rest_framework.authentication.TokenAuthentication in my REST_FRAMEWORK settings. I'm still using a custom authentication which fetches my user.
It looks like this (not going to format it. SO sucks at formatting code from VIM):
class CustomAuthentication(object):
def authenticate(self, email=None, password=None):
User = get_user_model()
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return None
if user.check_password(password):
return user
return None
def get_user(self, user_id):
try:
user_model = get_user_model()
user = user_model.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I used this Django docs to create that code: https://docs.djangoproject.com/en/1.10/topics/auth/customizing/
If you search for the error string in the DRF code, you find this (in authtoken/serializers.py:
from django.contrib.auth import authenticate
...
if username and password:
user = authenticate(username=username, password=password)
if user:
# From Django 1.10 onwards the `authenticate` call simply
# returns `None` for is_active=False users.
# (Assuming the default `ModelBackend` authentication backend.)
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg, code='authorization')
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg, code='authorization')
...
So it looks like depending on which version of Django you're using, either these credentials are incorrect, or the user is not active (for Django >= 1.10)?
Have you tried logging in manually in the admin with these credentials to verify them?
OK I solved it. Inside my settings, I just had to remove the AUTHENTICATIONS_BACKEND. I thought my custom backend was different for merely logging a user in and the token authentication backend worked to get that token.
I am using django-rest-jwt for authentication in my app.
By default it user username field to autenticate a user but I want let the users login using email or username.
Is there any mean supported by django-rest-jwt to accomplish this.
I know the last option would be write my own login method.
No need to write a custom authentication backend or custom login method.
A Custom Serializer inheriting JSONWebTokenSerializer, renaming the 'username_field' and overriding def validate() method.
This works perfectly for 'username_or_email' and 'password' fields where the user can enter its username or email and get the JSONWebToken for correct credentials.
from rest_framework_jwt.serializers import JSONWebTokenSerializer
from django.contrib.auth import authenticate, get_user_model
from django.utils.translation import ugettext as _
from rest_framework import serializers
from rest_framework_jwt.settings import api_settings
User = get_user_model()
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
class CustomJWTSerializer(JSONWebTokenSerializer):
username_field = 'username_or_email'
def validate(self, attrs):
password = attrs.get("password")
user_obj = User.objects.filter(email=attrs.get("username_or_email")).first() or User.objects.filter(username=attrs.get("username_or_email")).first()
if user_obj is not None:
credentials = {
'username':user_obj.username,
'password': password
}
if all(credentials.values()):
user = authenticate(**credentials)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
payload = jwt_payload_handler(user)
return {
'token': jwt_encode_handler(payload),
'user': user
}
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "{username_field}" and "password".')
msg = msg.format(username_field=self.username_field)
raise serializers.ValidationError(msg)
else:
msg = _('Account with this email/username does not exists')
raise serializers.ValidationError(msg)
In urls.py:
url(r'{Your url name}$', ObtainJSONWebToken.as_view(serializer_class=CustomJWTSerializer)),
Building on top of Shikhar's answer and for anyone coming here looking for a solution for rest_framework_simplejwt (since django-rest-framework-jwt seems to be dead, it's last commit was 2 years ago) like me, here's a general solution that tries to alter as little as possible the original validation from TokenObtainPairSerializer:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomJWTSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
credentials = {
'username': '',
'password': attrs.get("password")
}
# This is answering the original question, but do whatever you need here.
# For example in my case I had to check a different model that stores more user info
# But in the end, you should obtain the username to continue.
user_obj = User.objects.filter(email=attrs.get("username")).first() or User.objects.filter(username=attrs.get("username")).first()
if user_obj:
credentials['username'] = user_obj.username
return super().validate(credentials)
And in urls.py:
url(r'^token/$', TokenObtainPairView.as_view(serializer_class=CustomJWTSerializer)),
Found out a workaround.
#permission_classes((permissions.AllowAny,))
def signin_jwt_wrapped(request, *args, **kwargs):
request_data = request.data
host = request.get_host()
username_or_email = request_data['username']
if isEmail(username_or_email):
# get the username for this email by model lookup
username = Profile.get_username_from_email(username_or_email)
if username is None:
response_text = {"non_field_errors":["Unable to login with provided credentials."]}
return JSONResponse(response_text, status=status.HTTP_400_BAD_REQUEST)
else:
username = username_or_email
data = {'username': username, 'password':request_data['password']}
headers = {'content-type': 'application/json'}
url = 'http://' + host + '/user/signin_jwt/'
response = requests.post(url,data=dumps(data), headers=headers)
return JSONResponse(loads(response.text), status=response.status_code)
I check that whether the text that I received is a username or an email.
If email then I lookup the username for that and then just pass that to /signin_jwt/
authentication.py
from django.contrib.auth.models import User
class CustomAuthBackend(object):
"""
This class does the athentication-
using the user's email address.
"""
def authenticate(self, request, username=None, password=None):
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
return None
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
settings.py
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'app_name.authentication.CustomAuthBackend',
]
How it works:
If user try to authenticate using their username django will look at the ModelBackend class. However, if the user adds its email instead, django will try ModelBackend but will not find the logic needed, then will try the CustomAuthBackend class making it work the authentication.
Alternatively, this new DRF Auth project dj-rest-auth seems to provide support for log in by username or email through djangorestframework-simplejwt.
dj-rest-auth works better for authentication and authorization. By default dj-rest-auth provides - username, email and password fields for login. User can provide email and password or username and password. Token will be generated, if the provided values are valid.
If you need to edit these login form, extend LoginSerializer and modify fields. Later make sure to add new custom serializer to settings.py.
REST_AUTH_SERIALIZERS = {
'LOGIN_SERIALIZER': 'yourapp.customlogin_serializers.CustomLoginSerializer'
}
Configuring dj-rest-auth is bit tricky, since it has an open issue related to the refresh token pending. There is workaround suggested for that issue, so you can follow below links and have it configured.
https://medium.com/geekculture/jwt-authentication-in-django-part-1-implementing-the-backend-b7c58ab9431b
https://github.com/iMerica/dj-rest-auth/issues/97
If you use the rest_framework_simplejwt this is a simple mode. views.py
from rest_framework_simplejwt.tokens import RefreshToken
from django.http import JsonResponse
from rest_framework import generics
class EmailAuthToken(generics.GenericAPIView):
def post(self, request):
user_data = request.data
try:
user = authenticate(request, username=user_data['username_or_email'], password=user_data['password'])
if user is not None:
login(request, user)
refresh = RefreshToken.for_user(user)
return JsonResponse({
'refresh': str(refresh),
'access': str(refresh.access_token),
}, safe=False, status=status.HTTP_200_OK)
else:
return JsonResponse({
"detail": "No active account found with the given credentials"
}, safe=False, status=status.HTTP_200_OK)
except:
return Response({'error': 'The format of the information is not valid'}, status=status.HTTP_401_UNAUTHORIZED)
django-rest-framework makes use of django.contrib.auth for authentication and authorization (as stated in the django-rest-framework authentication api guide)
However, no-where in the documentation does it talk about how users are actually authenticated using the rest-framework
By default the django.contrib.auth views will respond with a server-side rendered login form.
However, if using a client-side framework such as AngularJs this is not desired - you simply want an api endpoint against which you can authenticate.
Questions:
Is there django-rest-framework documentation I am somehow missing which explains how user authentication is done-out-of-the-box?
Does an out-of-the-box solution even exist?
If not, what is the recommended way of achieving this with minimal reinvention of the wheel?
lets say that you have login view:
Note: with this method you have to assure SSL/TLS because username and password are sending as plain text.
import json
import requests
def login(request):
if request.method == "POST":
username = request.POST['username']
password = request.POST['password']
login_url = 'http://your_url:port/rest-api/login/'
response = requests.post(login_url, data={'username': username, 'password': password})
response = json.loads(response.text)
if response.status_code == 200:
return render_to_response("login.html", {"success": True}, RequestContext(request))
your view in rest-api:
from django.contrib.auth.backends import ModelBackend as DjangoModelBackend
def login(request):
response = base_response.copy()
username = request.DATA.get('username', '')
password = request.DATA.get('password', '')
user = DjangoModelBackend().authenticate(username=email, password=password)
if user is not None:
response["message"] = "Authenticated"
else:
response["message"] = "Login Failed"
return Response(response)
and here is the part of ModelBackend
from django.contrib.auth import get_user_model
class ModelBackend(object):
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.get_by_natural_key(username)
if user.check_password(password):
return user
except UserModel.DoesNotExist:
return None
You don't usually go through login forms when authenticating yourself at an API endpoint - you either use an API token or send the authentication credentials through a header, see How to use Basic Auth with jQuery and AJAX? on how to do that.
I want to use Tastypie's ApiKeyAuthentication to authenticate a request and then establish a session for the user within a Django view. I have username and api_key for the user. I do not have the user's password. This is the code I currently have:
class ApiKeyPlusWebAuthentication(ApiKeyAuthentication):
def is_authenticated(self, request, **kwargs):
isAuthenticated = super(ApiKeyPlusWebAuthentication, self).is_authenticated(request, **kwargs)
if isAuthenticated:
print request.user.email
return isAuthenticated
#login for access from UIWebView
def login_usingApiKeyAuthentication(request):
auth = ApiKeyPlusWebAuthentication(request)
if auth.is_authenticated(request):
print 'authenticated'
login(request, request.user)
return redirect(reverse(view_name))
else:
print 'NOT authenticated'
messages.error(request, MESSAGE_INVALID_LOGIN)
fail_redirect = redirect(reverse('login'))
return fail_redirect
I am getting an error 'User' object has no attribute 'backend'. This is because I haven't called authenticate(user, password). I am using the Django default authentication backend.
In this scenario, I only have APIKey associated with the user and don't have the raw password for authentication.
One way to handle this may be to create custom authentication backend that bypasses password requirement. However, registering a "password-less" authentication backend in settings.py seems like a hack prone to security breakdown.
So, how can I use ApiKeyAuthentication and then authenticate & login the user in Django establishing a session?
I found a solution to set the backend in another post. You can set the custom backend directly on the user object.
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
class PasswordlessAuthBackend(ModelBackend):
"""Log in to Django without providing a password.
"""
def authenticate(self, username=None):
try:
return User.objects.get(username=username)
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
#csrf_exempt
def login_uiwebview(request):
auth = ApiKeyPlusWebAuthentication(request)
if auth.is_authenticated(request):
view_name = request.POST.get('view_name')
request.user.backend = 'app.views.PasswordlessAuthBackend'
login(request, request.user)
return redirect(view_name)
else:
print 'NOT authenticated'
messages.error(request, MESSAGE_INVALID_LOGIN)
fail_redirect = redirect(reverse('login'))
return fail_redirect