PedroBern / django-graphql-auth

Django registration and authentication with GraphQL.
https://django-graphql-auth.readthedocs.io/en/latest/
MIT License
329 stars 106 forks source link

You do not have permission to perform this action, but I'm sending the token on protected mutation #109

Closed Instrumedley closed 3 years ago

Instrumedley commented 3 years ago

Prerequisites

For more information, see the CONTRIBUTING guide.

Hi guys, I'm having a hard time getting the library to work properly with regarding protected mutations or using the @login_required decorator. I'm probably doing something wrong but can't figure out why

This is the query for the mutation I'm testing mutation CreateSession{ createSession(category: "assessment",name: "test 2332", totalSteps: 23, trainingId: 1, userId: 6) { session { id name } } }

And here is the schema class for that mutation

class CreateSessionMutation(graphene.Mutation):
    class Arguments:
        # The input arguments for this mutation
        name = graphene.String(required=True)
        category = graphene.String(required=True)
        total_steps = graphene.Int(required=True)
        created_at = graphene.DateTime(required=False, default_value=datetime.datetime(2006, 1, 2, 15, 4, 5))
        training_id = graphene.Int(required=True)
        user_id = graphene.Int(required=True)

    # The class attributes define the response of the mutation
    session = graphene.Field(SessionType)

    @classmethod
    @login_required
    def mutate(cls, root, info, **kwargs):
        print(info.context.user)
        session = Session(name=kwargs.pop('name'),
                          category=kwargs.pop('category'),
                          total_steps=kwargs.pop('total_steps'),
                          created_at=kwargs.pop('created_at'),
                          training_id=kwargs.pop('training_id'),
                          user_id=kwargs.pop('user_id'))
        session.save()
        return CreateSessionMutation(session=session)

When testing on Insomnia, the Login, Me and VerifyAccount all work fine. If I send the Authentication JWT Token in the header, it gives success, if I don't it gives the error as expected.

However when testing that mutation, I keep getting "You do not have permission to perform this action". I'm sure I'm using the token correctly and it's not expired because at the same time I test this, I test the verify token and it verifies it correctly. I sent a screenshot in any case of the insomnia panels

Screenshot 2021-04-13 at 11 27 34 Screenshot 2021-04-13 at 11 27 25

Here is are the relevant parts of my settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'metrics',
    'graphene_django',
    'graphql_auth',
    'graphql_jwt.refresh_token.apps.RefreshTokenConfig',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
GRAPHENE = {
    'SCHEMA': 'gleechi_analytics.schema.schema',
    'MIDDLEWARE': [
        'graphene_django.debug.DjangoDebugMiddleware',
        'metrics.debug.DebugMiddleware',
        'graphql_jwt.middleware.JSONWebTokenMiddleware',
    ]
}
GRAPHQL_JWT = {
    "JWT_ALLOW_ANY_CLASSES": [
        "graphql_auth.mutations.Register",
        "graphql_auth.mutations.VerifyAccount",
        "graphql_auth.mutations.ResendActivationEmail",
        "graphql_auth.mutations.SendPasswordResetEmail",
        "graphql_auth.mutations.PasswordReset",
        "graphql_auth.mutations.ObtainJSONWebToken",
        "graphql_auth.mutations.VerifyToken",
        "graphql_auth.mutations.RefreshToken",
        "graphql_auth.mutations.RevokeToken",
        "graphql_auth.mutations.VerifySecondaryEmail",
    ],
    "JWT_VERIFY_EXPIRATION": True,
    "JWT_LONG_RUNNING_REFRESH_TOKEN": True,
}

AUTHENTICATION_BACKENDS = [
    'graphql_jwt.backends.JSONWebTokenBackend',
    'django.contrib.auth.backends.ModelBackend',
    'graphql_auth.backends.GraphQLAuthBackend'
]

GRAPHQL_JWT = {
    "JWT_VERIFY_EXPIRATION": True,
    "JWT_LONG_RUNNING_REFRESH_TOKEN": True,
}

And finally my requirements.txt

aniso8601==7.0.0 asgiref==3.2.10 attrs==20.1.0 bcrypt==3.1.7 beautifulsoup4==4.9.3 cached-property==1.5.1 cachetools==4.1.1 certifi==2019.11.28 cffi==1.14.0 chardet==3.0.4 click==7.1.2 colorama==0.4.4 coreapi==2.3.3 coreschema==0.0.4 cryptography==2.8 defusedxml==0.6.0 dj-database-url==0.5.0 Django==3.0.4 django-adaptors==0.2.5 django-allauth==0.41.0 django-cors-headers==3.2.1 django-csvimport==2.13 django-debug-toolbar==3.2 django-filter==2.2.0 django-graphiql-debug-toolbar==0.1.4 django-graphql-auth==0.3.16 django-graphql-jwt==0.3.0 django-health-check==3.16.2 django-heroku==0.0.0 django-postgres-metrics==0.6.2 django-rest-auth==0.9.5 django-rest-framework==0.1.0 django-rest-swagger==2.2.0 django-storages==1.9.1 djangorestframework==3.11.0 djangorestframework-gis==0.15 djangorestframework-jwt==1.11.0 docker-pycreds==0.4.0 dockerpty==0.4.1 docopt==0.6.2 drf-spectacular==0.13.1 drf-yasg==1.17.1 geojson==2.5.0 google-api-core==1.22.1 google-auth==1.20.1 google-cloud-core==1.4.1 google-cloud-storage==1.26.0 google-resumable-media==0.5.1 googleapis-common-protos==1.52.0 graphdoc==0.2.1 graphene==2.1.8 graphene-django==2.15.0 graphql-core==2.3.2 graphql-relay==2.0.1 gunicorn==20.0.4 idna==2.9 inflection==0.5.1 ingredient-parser==1.0.1 itypes==1.1.0 Jinja2==2.11.1 joblib==1.0.1 jsonschema==3.2.0 markdown2==2.4.0 MarkupSafe==1.1.1 nltk==3.5 oauthlib==3.1.0 openapi-codec==1.3.2 packaging==20.4 paramiko==2.7.1 parse-ingredients==0.0.3 Pillow==7.0.0 promise==2.3 protobuf==3.13.0 psycopg2-binary==2.8.5 pyasn1==0.4.8 pyasn1-modules==0.2.8 pycparser==2.19 Pygments==2.6.1 PyJWT==1.7.1 PyMySQL==1.0.2 PyNaCl==1.4.0 pyparsing==2.4.7 pyrsistent==0.16.0 python-dotenv==0.15.0 python3-openid==3.1.0 pytz==2019.3 PyYAML==5.3 regex==2020.11.13 requests==2.23.0 requests-oauthlib==1.3.0 rsa==4.6 ruamel.yaml==0.16.10 ruamel.yaml.clib==0.2.0 Rx==1.6.1 simplejson==3.17.0 singledispatch==3.6.1 six==1.14.0 soupsieve==2.1 sqlparse==0.3.1 termcolor==1.1.0 text-unidecode==1.3 texttable==1.6.2 tqdm==4.57.0 uritemplate==3.0.1 urllib3==1.25.8 uWSGI==2.0.18 websocket-client==0.56.0 whitenoise==5.0.1

Any clue or help would be much appreciated. Thank you for your time

Instrumedley commented 3 years ago

So, I fixed this now by modifying this line in my url

path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))), to this

path('graphql/', jwt_cookie(GraphQLView.as_view(graphiql=True))),

The former is how the tutorial instructed me to do. But I'm not sure I quite understand the full thing.

When testing on Insomnia I do get a "don't have permission" message if I try to call that mutation without calling login first. If I call Login and then call CreateSession then it works, however I don't need to set Authentication header with the JWT Token, so I'm not really sure now if this is the intended behaviour. I do get to see the JWT there if I do

print(info.context.headers)

{'Content-Length': '236', 'Content-Type': 'application/json', 'Host': 'localhost:8000', 'User-Agent': 'insomnia/2020.5.2', 'Cookie': 'csrftoken=Np24LyNrajUtp4OIqnrX9aFLTY7p0RAqIUJx2pHX6qsB0M6KN5OYio1elI8dwL3A; JWT=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImdsZWVjaGktYWRtaW4iLCJleHAiOjE2MTgzMTgwOTgsIm9yaWdJYXQiOjE2MTgzMTc3OTh9.e97blG9c3UxGovosXhlL9dRf7_GuhAkE9lbtvAOxRCc', 'Accept': '/'}

Is it some sort of behaviour from Insomnia or what am I missing here?

Instrumedley commented 3 years ago

Reading the JWT official docs clarified my confusion.

A cookie-based authentication does not require sending the tokens as a mutation input argument nor in the headers

Well, for now I'll stick to that since it works. To be honest, your guide and documentation needs a lot of work. By looking at my issue and most of the other issues here I can see your documentation is the one creating some of these confusions.