Skip to content

[BUG] Django Ninja JWT Token Validation Issue #117

@subham1099

Description

@subham1099

Description

When using Django Ninja JWT with a custom token obtain pair schema, the validation is being bypassed due to input type mismatch, leading to authentication errors.

Environment

  • Python: 3.12
  • Django: 5.1.6
  • django-ninja: 1.3.0
  • django-ninja-jwt: 5.3.5
  • pydantic: 2.9.2

Issue

The TokenObtainInputSchemaBase.validate_inputs method expects the input to be a dictionary, but in the current version of Django Ninja, the input is wrapped in a DjangoGetter object. This causes the validation to be bypassed, leading to a NoneType error when trying to authenticate.

Code

class TokenObtainPairInputSchema(TokenObtainInputSchemaBase):
      """Custom schema for token obtain pair."""
      @pyd.model_validator(mode="before")
      def validate_inputs(cls, values: DjangoGetter) -> DjangoGetter:
          input_values = values.obj
          request = values.context.get("request")
          # This condition is never true because input_values is a DjangoGetter
          if isinstance(input_values, dict):  # <--
              values.obj.update(
                  cls.validate_values(request=request, values=input_values)
              )
              return values
          return values

    @classmethod
    def get_response_schema(cls) -> type[Schema]:
        return TokenObtainPairOutputSchema

    @classmethod
    def get_token(cls, user: AbstractUser) -> dict[str, t.Any]:
        values = {}
        refresh = RefreshToken.for_user(user)
        values["refresh"] = str(refresh)
        values["access"] = str(refresh.access_token)
        values.update(
            user=UserSchema.from_orm(user)
        )  # this will be needed when creating output schema
        return values

Request

curl -X 'POST' \
'http://localhost:8001/api/v1/auth/token/pair' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"username": "string",
"password": "string"
}'

Error Log

[debug ] Input validation - values type: <class 'ninja.schema.DjangoGetter'>
[debug ] Input validation - input_values type: <class 'ninja.schema.DjangoGetter'>
[debug ] Input validation - input_values: <DjangoGetter: {'password': 'string', 'username': 'string'}>
[error ] 'NoneType' object has no attribute 'id'

Traceback (most recent call last):
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja/operation.py", line 119, in run
    values = self._get_values(request, kw, temporal_response)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja/operation.py", line 288, in _get_values
    data = model.resolve(request, self.api, path_params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja/params/models.py", line 57, in resolve
    return cls.model_validate(data, context={"request": request})
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/pydantic/main.py", line 641, in model_validate
    return cls.__pydantic_validator__.validate_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja/schema.py", line 228, in _run_root_validator
    return handler(values)
           ^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja_jwt/schema.py", line 117, in post_validate
    return cls.post_validate_schema(values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja_jwt/schema.py", line 128, in post_validate_schema
    data = cls.get_token(cls._user)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/app/app/api/auth/token_obtain.py", line 93, in get_token
    refresh = RefreshToken.for_user(user)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja_jwt/tokens.py", line 189, in for_user
    user_id = getattr(user, api_settings.USER_ID_FIELD)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'id'

Expected Behavior

The validation should handle both dictionary and DjangoGetter inputs, ensuring proper validation before authentication attempts.

Current Workaround

We've implemented a workaround by explicitly handling the DjangoGetter case:

@pyd.model_validator(mode="before")
def validate_inputs(cls, values: DjangoGetter) -> DjangoGetter:
    input_values = values.obj
    request = values.context.get("request")
    # Handle both dict and DjangoGetter inputs
    if isinstance(input_values, dict):
        values_dict = input_values
    else:
        # Convert DjangoGetter to dict
        values_dict = input_values._obj

    validated_values = cls.validate_values(request=request, values=values_dict)
    values.obj = validated_values
    return values

Questions

  1. Is this the intended behavior of the input validation?
  2. Should the base implementation be updated to handle DjangoGetter inputs?
  3. Is there a better way to handle this validation in custom schemas?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions