flavors / django-graphql-jwt

JSON Web Token (JWT) authentication for Graphene Django
https://django-graphql-jwt.domake.io
MIT License
820 stars 173 forks source link

Can't obtain token even with valid login credentials #240

Open Cimmanuel opened 3 years ago

Cimmanuel commented 3 years ago

Hello! I am using Django 3.1.4 alongside the most recent version of this library and trying to obtain a token but I keep getting:

{
  "errors": [
    {
      "message": "Please enter valid credentials",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "tokenAuth"
      ]
    }
  ],
  "data": {
    "tokenAuth": null
  }
}

I have done everything right but this keeps happening. I am using a custom user model and I have created my own authentication backend as mentioned in #46. If I try logging in with an email, it throws that error, but if I use a username, it works fine. Anyone experiencing something similar?

Here's my setup:

...
# settings.py

AUTHENTICATION_BACKENDS = [
    "graphql_jwt.backends.JSONWebTokenBackend",
    "django.contrib.auth.backends.ModelBackend",
    "apps.auth.EmailBackend",
]

GRAPHENE = {
    "SCHEMA": "cnapi.schema.schema",
    "SCHEMA_INDENT": 4,
    "MIDDLEWARE": ["graphql_jwt.middleware.JSONWebTokenMiddleware"],
}

GRAPHQL_JWT = {
    "JWT_VERIFY_EXPIRATION": True,
    "JWT_EXPIRATION_DELTA": timedelta(days=30),
    "JWT_PAYLOAD_HANDLER": "apps.accounts.auth.jwt_payload",
    "JWT_PAYLOAD_GET_USERNAME_HANDLER": (
        lambda payload: payload.get("email") or payload.get("mobile")
    ),
    "JWT_GET_USER_BY_NATURAL_KEY_HANDLER": "apps.auth.get_user_by_natural_key",
}

# backends.py

class EmailBackend(BaseBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is not None:
            try:
                user = get_user_model().objects.get(email__iexact=username)
            except ObjectDoesNotExist:
                """Not found, try another backend"""
            else:
                if user.check_password(password) and user.is_active:
                    return user
        return None

def get_user_by_natural_key(username):
    try:
        return get_user_model().objects.get(
            Q(email__iexact=username) | Q(mobile__iexact=username)
        )
    except ObjectDoesNotExist:
        return None

def jwt_payload(user, context=None):
    payload = {
        "mobile": user.mobile,
        "email": user.email,
        "exp": datetime.utcnow() + jwt_settings.JWT_EXPIRATION_DELTA,
    }

    if jwt_settings.JWT_ALLOW_REFRESH:
        payload["origIat"] = timegm(datetime.utcnow().utctimetuple())

    if jwt_settings.JWT_AUDIENCE is not None:
        payload["aud"] = jwt_settings.JWT_AUDIENCE

    if jwt_settings.JWT_ISSUER is not None:
        payload["iss"] = jwt_settings.JWT_ISSUER

    return payload

# schema.py (app)

class Mutation(graphene.ObjectType):
    token_auth = graphql_jwt.ObtainJSONWebToken.Field()
    verify_token = graphql_jwt.Verify.Field()
    refresh_token = graphql_jwt.Refresh.Field()

# schema.py (root)

class Query(apps.accounts.schema.Query, graphene.ObjectType):
    pass

class Mutation(apps.accounts.schema.Mutation, graphene.ObjectType):
    pass

schema = graphene.Schema(
    query=Query,
    mutation=Mutation,
)
thclark commented 6 months ago

I've experienced exactly the same. I think this field is fundamentally broken. The only thing that I can do is to remove the token_auth decorator on the mutation:


class ObtainJSONWebToken(mixins.ResolveMixin, JSONWebTokenMutation):
    """Obtain JSON Web Token mutation"""
    # By default this has a @token_auth decorator
    @classmethod
    def mutate(cls, root, info, **kwargs):
        return cls.resolve(root, info, **kwargs)

That bypasses the credentials problem but unfortunately then I get:

{'errors': [{'message': 'Cannot return null for non-nullable field ObtainJSONWebToken.token.', 'locations': [{'line': 4, 'column': 21}], 'path': ['token_auth', 'token']}], 'data': {'token_auth': None}}

Presumably because there doesn't seem to be any code there to actually get the token and return it. It's nuts.