simplejwt token work for all tenants for django-tenants

201 views Asked by At

I just want to mention about my problem. I have already researched many documents but I could not find any solution.

I just try to use django-tenants. Everything seems OK. But If any user gets the token after login, that token works for all tenants.

It's a big leak of security.

I have been thinking about an idea SIGNING_KEY. If I can change SIGNING_KEY for each tenant, it may be fixed. But It did not.

class TenantJWTAuthenticationMiddleware(MiddlewareMixin):
  def process_request(self, request):
    tenant = Client.objects.get(schema_name=connection.schema_name)
    jwt_secret_key = tenant.jwt_secret_key
    settings.SIMPLE_JWT['SIGNING_KEY'] = jwt_secret_key

This is my Middleware to change SIGNING_KEY for each tenant.

class Client(TenantMixin):
  name = models.CharField(max_length=100)
  paid_until =  models.DateField()
  on_trial = models.BooleanField()
  created_on = models.DateField(auto_now_add=True)
  jwt_secret_key = models.CharField(max_length=100, null=True, blank=True)

  # default true, schema will be automatically created and synced when it is saved
  auto_create_schema = True

class Domain(DomainMixin):
  pass

This is my model.

So, I added jwt_secret_key into my model and I got this field in Middleware and tried to set SIGNING_KEY for jwt. But still, jwt after user login can be used for all tenants.

Does anyone have any idea about my problem? Any suggestion or amendment to my solution?

Thanks.

2

There are 2 answers

1
Bartosz Kozłowski On

You can pass schema data to a jwt token based on the db connection by creating a custom serializer inheriting from djangorestframework-simplejwt's TokenObtainPairSerializer and passing additional data in get_token function. You can then use this serializer in a custom view inheriting from TokenObtainPairView.

from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from django.db import connection


class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        token["tenant_schema"] = connection.schema_name
        return token


class CustomTokenObtainPairView(TokenObtainPairView):

    serializer_class = CustomTokenObtainPairSerializer
    token_obtain_pair = TokenObtainPairView.as_view()

Then use this view to obtain the token

path("token/", CustomTokenObtainPairView.as_view(), name="token_obtain_pair")

To validate this token you have to create a custom authentication class for django rest framework and compare the access token tenant_schema to the current connection schema (from another tenant) and if schemas are different don't return the authentication data.

from rest_framework_simplejwt.authentication import JWTAuthentication
from django.db import connection

class CustomJWTAuthentication(JWTAuthentication):

    def authenticate(self, request):
        if auth := super().authenticate(request):
            user, token = auth
            if token.payload["tenant_schema"] == connection.schema_name:
                return user, token
        return None

Then change your DEFAULT_AUTHENTICATION_CLASSES in the project's settings

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "path.to.CustomJWTAuthentication",
    ),
}

I think you can achieve something similar with a jwt_secret_key field as you can get a tenant object just like you did in your middleware with a connection.schema_name and add any tenant data into a token. However, I think a schema name should be enough and there's no need to add additional fields to the tenant model for jwt tokens.

0
Alim Conde On

I recently encountered the same issue while working with django-rest-framework-simplejwt and django-tenants. Upon investigation, I realized that the problem lies within django-rest-framework-simplejwt. It generates tokens using user IDs, which can lead to unexpected behavior when dealing with multi-tenant applications.

Here's what I discovered during my debugging process: If two tenants have users with identical user IDs, the token generated for one tenant can inadvertently grant access to another tenant's resources if they share the same user ID. To illustrate this, I created two users in 'tenant1' and one user in 'tenant2'. When attempting to access 'tenant2' data using a token from 'tenant1' associated with user ID_1, the request was successful. However, the user with ID_2 from 'tenant1' couldn't access 'tenant2' data, likely because 'tenant2' had no user with ID_2.

Strangely, after creating a second user for 'tenant2', the request became successful without any modifications to the token. This behavior is problematic because it poses security risks and can lead to data leakage between tenants.

It's crucial to address this issue and find a robust solution to ensure proper isolation between tenants and prevent unauthorized access. Careful consideration and potential customization of the authentication and authorization mechanisms might be necessary to resolve this problem effectively."

Please share if you find a workaroud.