jwt token authorization is not working properly with custom role - django

I am using django-rest-framework and default auth_user table of Django and I create new table to manage new user_role and I extend it using auth_user.id to manage more roles like (teacher, student, staff, admin, school) but Now In Django there is only 3 decorators permission_classes (IsAuthenticated, AllowAny, IsAdminUser) to authenticate different role users to authenticate user.
I am using below JWT Token Module in my Django App.
JWT TOKEN: https://django-rest-framework-simplejwt.readthedocs.io/en/latest/
Solution which I think: first make sure user is authenticated or login already, using IsAuthenticated decorator permission_classes and then I filter table and check user have enough permission to access specific route or not on basis of user role from extended user table
ISSUE: I need to filter data again and again in every route to check user role.
Please let me know best approach to manage this thing

What you think the solution should be is how I would approach it. IsAdminUser is built into django-rest-framework but when using it with a JWT library, it makes more sense to only check IsAuthenticated. I am currently working on something similar in an application with multiple roles.
# models.py
from django.db import models
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
class MyUser(AbstractBaseUser, PermissionsMixin):
ADMIN = 1
MANAGER = 2
EMPLOYEE = 3
ROLE_CHOICES = (
(ADMIN, 'Admin'),
(MANAGER, 'Manager'),
(EMPLOYEE, 'Employee'),
)
role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, blank=True)
# Other fields omitted
If your users can have many roles, you can create a role table and do a ManyToMany field. Once you get to your View logic, you could do something like this:
# views.py
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenicated
from rest_framework.response import Response
class SomeApiView(APIView):
# declare serializer
permission_classes = (IsAuthenticated, )
def get(self, request):
user = request.user
if user.role != 1:
return Response({}, HTTP_403_FORBIDDEN)
else:
# get logic for requested view

Related

How to store and authenticate different user types in Django?

There are two different types of users, A & B. Users of type A authenticate using username and password while type B have email and password. The project uses token based authentication.
The question is: how to manage both user types simultaneously?
The approaches I have considered:
Separate models for both user types and implement a custom auth backend to authenticate user signing in using email
Single user model to cater for both user types. However, this would mean I have to an add additional field (email) and populate some random value for users of type A
Apart from this, I have to store some custom information for logged in users like device type, location etc. Is it possible to override authtoken_token table to store this info or I have to create a separate model for that?
This following implementation should work (untested):
from rest_framework import serializers as rfs
from rest_framework.decorators import api_view
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from django.contrib.auth import get_user_model
User = get_user_model()
### serializers.py
class UserSerializer(rfs.ModelSerializer):
class Meta:
model = User
fields = ["username", "email"]
class UserLoginSerializer(rfs.Serializer):
username = rfs.CharField(required=False)
email = rfs.EmailField(required=False)
password = rfs.CharField(style={"input_type": "password")
user = UserSerializer(read_only=True)
def validate(self, data):
username = data.get("username", None)
email = data.get("email", None)
# case: both were given
if username and email:
raise rfs.ValidationError("Only one of email or username should be given.")
# case none were given
elif (not username and not email):
raise rfs.ValidationError("One of email or username is required.")
elif username:
data["user"] = User.objects.get(username=username)
elif email:
data["user"] = User.objects.get(email=email)
return data
### views.py
#api_view(["POST"])
def login_view(request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
"token": token.key,
"user_id": user.pk,
})
Explanation:
Basically, we have created a serializer that checks that only of either username or email is POSTed by the user.
For your 2nd question,
I have to store some custom information for logged in users like device type, location etc.
you can probably create a model such as:
from django.db import models
from rest_framework.authtoken.models import Token
class UserTokenClient(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="token_client")
token = models.ForeignKey(Token, related_name="client")
## extra fields you want
device_type = models.CharField()
or, you can use the package I wrote django-rest-durin which tries to solve this problem by associating a Client model between each User and Token.
You can easily subclass the Client model from the models.py to add your own custom fields, like so:
from durin.models import Client
class CustomClient(Client):
## extra fields you want
device_type = models.CharField()
and then override the LoginView similar to how it is done in this project's code.

How to make self.request.user return the user attached to token?

I am using token authentication in Django Rest Framework, and am passing a token into the header of my request. This token is attached to a user. How would I make it so that when 'self.request.user' is called, the user attached to the token in the request is returned?
Failing this, more specifically I need some sort of way to change the following 'perform_create()' function in my view to instead set 'author' to the user attached to the token.
perform_create() function currently:
def perform_create(self, serializer):
serializer.save(author = self.request.user)
I need it so 'author' is set to the user attached to the token in the header.
Any help would be much appreciated.
Thanks
EDIT: I am unable to use session based authentication in this implementation
I use the exact same code (with type checking at runtime) to get the user model in a token authentication system and can confirm that it works.
def perform_create(self, serializer: ModelSerializer) -> None:
serializer.save(registrant=self.request.user)
registrant is the Foreign Key to the user model:
# models.py
from django.conf import settings
from django.db import models
class Model(models.Model):
# ...
registrant = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True)
# ...
EDIT: This is further confirmed in the documentation:
If successfully authenticated, TokenAuthentication provides the following credentials.
request.user will be a Django User instance.
request.auth will be a rest_framework.authtoken.models.Token instance.

How do i also get user data with token "Key" in http response to django rest_auth login endpoint?

I am very new to working with django. i have developed an API for user Registration, Login and Logout with Django Rest-Auth Using Custom User Model extending AbstractUser.
When i call POST on Login API endpoint using credential, i get token in response, Please check below screenshot.
POST on Login API Endpoint
I also need to get Logged in User data. how do i do that!?
Any help would do.
TIA.
You can override the default TokenSerializer with a custom serializer that will include users.
in a file say yourapp/model.py
from django.conf import settings
from rest_framework import serializers
from rest_auth.models import TokenModel
from rest_auth.utils import import_callable
from rest_auth.serializers import UserDetailsSerializer as DefaultUserDetailsSerializer
# This is to allow you to override the UserDetailsSerializer at any time.
# If you're sure you won't, you can skip this and use DefaultUserDetailsSerializer directly
rest_auth_serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {})
UserDetailsSerializer = import_callable(
rest_auth_serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer)
)
class CustomTokenSerializer(serializers.ModelSerializer):
user = UserDetailsSerializer(read_only=True)
class Meta:
model = TokenModel
fields = ('key', 'user', )
and in your app settings use rest-auth configuration to override the default class
yourapp/settings.py
.
.
.
.
REST_AUTH_SERIALIZERS = {
'TOKEN_SERIALIZER': 'yourapp.models.CustomTokenSerializer' # import path to CustomTokenSerializer defined above.
}
You need to implement your custom view and serializer to do that.
First, you create your serializer for response with key and user data.
Since we don't have info on your model, this is just basic example:
class MySerializer(serializes.ModelSerializer):
key = serializers.SerializerMethodField()
def get_key(self, obj):
return create_token(obj)
class Meta:
model= User
fields = ( 'id', 'username', 'key', )
here the get_key can generate your token ( obj will be instance of your user model )
Then, I would suggest to look at how django-rest-auth implements LoginView which you use to get the token now.
And you create new view and take inspiration from LoginView implementation to understand how to obtain the token. And you use the serializer to create response you want.

Django Rest Framework User and UserRole and Permission Handling using Token Authentication

I am newbie to Django Rest Framework. I googled a lot and stuck on How to set User Role to User and strict to Permission.
My question is very basic but I don't know how to do it.
I want to create 2 types of Users:
1) Admin (Full Access)
2) Normal User (Limited Access)
I found some links but still on getting what to do:
Django rest-framework per action permission
django-rest-framework : setting per user permissions
you can separate users by is_staff property and use, standart rest permission isadminuser, for test you try this simple view
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView
class ExampleView(APIView):
permission_classes = (IsAdminUser,)
def get(self, request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)

Key based access for the Django Rest Framework

Lets say the API endpoint to GET a list of users is this
/api_auth/user/
But I want to restrict access to this list only to people with an api_key
/api_auth/user/?access_key=$omeRandomHash3252532
How do I implement such an access system using the Django Rest Framework?
Should I use Permissions to implement this?
This is not supported out of the box for django-rest-framework, however it can easily be implemented:
If you take a look at http://www.django-rest-framework.org/api-guide/authentication/ you'll see an Example of a custom authentication method. Baased on that, you'll need to implement something like this:
from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions
class APIKeyAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
api_key = request.GET.get('api_key')
if not api_key:
return None
try:
user = get_user_from_api_key(api_key)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No user for API KEY')
return (user, None)
The APIKeyAuthentication should be put on an authentication.py module and be configured with REST_FRAMEWORK setting on settings.py, like this
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'my_custom_package.authentication.APIKeyAuthentication',
)
}
Now, what the above does is that it checks if the api_key parameter is present (if not it will return None to check if the request can be authenticated differently -- if you don't want to check any other authentication classes then just raise an exceptions.AuthenticationFailed exception like we do when we dont find the user below. Now, we need to implement a get_user_from_api_key function that will return a User instance from an API_KEY. If the user that is correlated with the passed api_key is found then it will be returned, if not an exceptions.AuthenticationFailedexception will be thrown.
Concerning the get_user_from_api_key function, its implementation depends on your requirements. For instance, if you want to create a new api key for each user, you should create an APIKey model that will have an api_key CharField and a ForeignKey to the User that has this api_key. The get_user_from_api_key function then will query the APIKey model to get the user with the provided api_key.
Update
If you want to use the django-rest-framework permissions instead of the authentication, you may create an APIKeyPermission class like this:
from rest_framework import permissions
class APIKeyPermission(permissions.BasePermission):
def has_permission(self, request, view):
api_key = request.GET.get('api_key')
return check_permission(api_key, request)
Where the check_permission function will check if the api_key passed has permissions for that specific request. Please check the examples on http://www.django-rest-framework.org/api-guide/permissions/ for more info - you may instead choose to implement has_object_permission to implement object-level permissions instead of view-level permissions.
If you are able to set a header in your request you can use Rest Framework's Token Authentication.
Otherwise, if you need to put it in the URL as a GET-paramter you could make your own custom authentication class:
from rest_framework.authentication import TokenAuthentication
class MyAuthentication(TokenAuthentication):
def authenticate(self, request):
token = request.GET.get('api-key', None)
if token is None:
return None
return self.authenticate_credentials(token)