Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Has anyone implemented this experimental feature JWTTokenUserAuthentication backend? #307

Closed
Abishek05 opened this issue Sep 25, 2020 · 14 comments
Labels

Comments

@Abishek05
Copy link

So I've been trying to build a Django project that handles authentication centrally on a standalone basis using django-rest-framework-simplejwt. And other Django Rest Framework projects that use this for authentication. All projects will have their own databases.

I am not quite sure what goes into the database section in settings.py of both the auth project and other projects. The documentation mentions something about JWTTokenUserAuthentication backend as an experimental feature and is quite inadequate.

I have done some research and found I may have to use a remote user login or set up a proxy server. Can someone point me in the right direction?

@Andrew-Chen-Wang
Copy link
Member

Andrew-Chen-Wang commented Sep 25, 2020 via email

@Abishek05
Copy link
Author

@Andrew-Chen-Wang Thanks for your response.

In a microservices environment, multiple Django projects will be hosted separately with their own separate database and will not contain the user table to check the user ID.

Only one Django project(central authentication) will contain the user table along with the user ID, in this scenario, I suppose django-rest-framework-simplejwt's JWTTokenUserAuthentication can be used for SSO(Single Sign-On) according to the documentation provided here.

So, I am wondering what other settings I must do in order for this to lookup user ID from this separately hosted Django project(meant only for authentication) apart from

REST_FRAMEWORK = {
    ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        ...
        'rest_framework_simplejwt.authentication.JWTTokenUserAuthentication',
    )
    ...
}

included in the documentation.

@Andrew-Chen-Wang
Copy link
Member

Uh Django is more typically known for being a monolith type web framework. If I'm understanding this correctly, you only have one server with the user table; the rest of your servers have a different database without the user table right?

In that case, if you use JWTTokenUserAuthentication, what you have now is perfectly fine. That means SimpleJWT only uses PyJWT to make sure that the token is valid. Just make sure you set the SIGNING_KEY to be the same across all you servers (assuming you have a different SECRET_KEY for each Django, which SimpleJWT defaults to if you don't set a signing key).

There's an upcoming PR regarding setting audiences, but I wouldn't count on that just yet since, although it's in production, it's not been tested by me. Hope that helps!

@Abishek05
Copy link
Author

@Andrew-Chen-Wang Yes, you got it correct. There is one server with a user table and the rest of the servers hosts its own application-related services without the user table similar to Google, how one account is used for multiple products like Gmail, Drive, etc.

So if I understand you correctly, all I need to do is make sure SECRET_KEY (SIGNING_KEY) needs to be set the same across all the project's settings.py file along with 'rest_framework_simplejwt.authentication.JWTTokenUserAuthentication'.

And when you say it's not tested enough and wouldn't count on it, do you mean there is a security risk with this kind of architecture(JWTTokenUserAuthentication). I am assuming that is if the SECRET_KEY is compromised or any other thing?

@Andrew-Chen-Wang
Copy link
Member

So if I understand you correctly, all I need to do is make sure SECRET_KEY (SIGNING_KEY) needs to be set the same across all the project's settings.py file along with 'rest_framework_simplejwt.authentication.JWTTokenUserAuthentication'

Yes, however I encourage you to generate your own SIGNING_KEY rather than using the SECRET_KEY since you want to rotate each of these keys. You can generate a new secret key by just generating another Django project or looking for their util function that generates the secret key. I say this since you need to look at the requirements for your encryption standard to make sure your key is long enough (e.g. HS256 and HS512 require different length keys).

similar to Google, how one account is used for multiple products like Gmail, Drive, etc.

At Google, we only give a user one account that's used across all subdomains. Even when you take into account YouTube.com, the table is the same, but the architecture is a little different than when we think typical relational database ;)

And when you say it's not tested enough and wouldn't count on it, do you mean there is a security risk

Sorry, I was referring to the new PR, not this feature. This feature has been tested enough to be OK.

@Abishek05
Copy link
Author

@Andrew-Chen-Wang Noted! Thanks for the info and for pointing me in the right direction.

@Abishek05
Copy link
Author

@Andrew-Chen-Wang Is there a way to use the token user received from the JWT token in models.py of microservices Django project so as to establish a relationship with the fake User model?

I'm looking to do something like this:-

models.py

from django.db import models
"""Don't want to use this User instance"""
# from django.contrib.auth.models import User


class Book(models.Model):
    user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
    name = models.CharField(max_length=120)
    price = models.FloatField()

@Abishek05 Abishek05 reopened this Sep 28, 2020
@Andrew-Chen-Wang
Copy link
Member

The TokenUser model that you get from using JWTTokenUserAuthentication backend comes from here: https://github.com/SimpleJWT/django-rest-framework-simplejwt/blob/969118276fcbd49f4f5d88756dd36f2427e25dbd/rest_framework_simplejwt/models.py#L9-L17

It's similar to how if an anonymous user pings a view, then you'll get django.contrib.auth.models.AnonymousUser. What you should do is that you need to grab the ID by doing:

tokenuser.id

And then using your own model, do User.objects.get(id=tokenuser.id)

@Abishek05
Copy link
Author

@Andrew-Chen-Wang I am able to fetch the user.id from token as you suggested but the model tries to lookup the user table in its own database and throws an error django.db.utils.IntegrityError: insert or update on table "core_book" violates foreign key constraint "core_book_user_id_55b2f8ca_fk_auth_user_id" DETAIL: Key (user_id)=(1) is not present in table "auth_user"

Here's how my code looks:-

models.py

from django.db import models
from django.contrib.auth.models import User
# Create your models here.


class Book(models.Model):
    user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
    name = models.CharField(max_length=120)
    price = models.FloatField()

views.py

from django.shortcuts import render
from rest_framework import viewsets, permissions
from .models import Book
from .serializers import BookSerializer
from django.contrib.auth.models import User
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST

# Create your views here.


class BookViewset(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = [permissions.IsAuthenticated]

    # def get(self, request):
    #     print("hello")
    #     print(self.request.user)
    #     return self.request.user

    def post(self, request, *args, **kwargs):
        user = User.objects.get(id=tokenuser.id)
        print("user")
        print(user)
        return Response({"message": ""}, status=HTTP_200_OK)

@Andrew-Chen-Wang
Copy link
Member

Andrew-Chen-Wang commented Sep 29, 2020

That's because you should be doing request.user.id in this case. tokenuser was just an example variable; otherwise, you've got a variable that was never even declared.

You should also not have default=1 in your FK attribute. That's just dangerous...

@oscarychen
Copy link
Contributor

oscarychen commented Aug 2, 2021

@Andrew-Chen-Wang What would be a good way to deal with custom attributes on a user model that's extended from Django's AbstractUser?

For example if I have a custom user model, like:

class CustomUser(django.contrib.auth.models.AbstractUser):
    has_agreement = BooleanField(default=False)

Normally if Im just using JWTAuthentication, in one of my Rest API, I can look at this attribute with request.user.has_agreement. But the TokenUser class does not have any of these attributes.

Is there some way I can imbed these attributes into the JWT? and have the TokenUser instantiate from JWT with these attributes? Is this what the Customizing Claim is for(https://django-rest-framework-simplejwt.readthedocs.io/en/latest/customizing_token_claims.html)?

Thanks in advance.

@Andrew-Chen-Wang
Copy link
Member

Andrew-Chen-Wang commented Aug 2, 2021

@oscarychen Sorry this isn't in the documentation; there is a setting called: TOKEN_USER_CLASS where the value should a dotted path to your subclass module. You can subclass TokenUser at from rest_framework_simplejwt.models import TokenUser. Hope that helps. (The other approach you mentioned about customizing token claims is also possible; in that case, you'd need to subclass our serializers and views and customize to your liking. Both methods work!).

note: added to PR #440

@oscarychen
Copy link
Contributor

oscarychen commented Aug 3, 2021

@Andrew-Chen-Wang Would I need to also subclass the AccessToken class to add custom attributes onto the access token?

I went with the route of subclassing the TokenUser as you have mentioned. While my API end points are trying to pull the user's custom attributes from JWT token, the login endpoint isn't putting the custom attributes into the token unless I also subclass the serializer to add custom claims.

So I'm not sure if the proper way would be to subclass the AccessToken to also include the custom attributes for the custom user model instead of adding custom claims in the serializer?

Thanks again!

@Andrew-Chen-Wang
Copy link
Member

@oscarychen np! I'd recommend subclassing the serializer. It allows for more flexibility in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants