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

How to use Graphene with Django REST Framework authentication #249

Closed
rstuven opened this issue Aug 18, 2016 · 20 comments
Closed

How to use Graphene with Django REST Framework authentication #249

rstuven opened this issue Aug 18, 2016 · 20 comments

Comments

@rstuven
Copy link

rstuven commented Aug 18, 2016

I got some REST API endpoints in Django and I wanted to use the same authentication for Graphene. The documentation does not provides any guidance.

I have authentication_classes = (TokenAuthentication,) in my API views. This was my solution:

urls.py:

# ...
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import authentication_classes, permission_classes, api_view

def graphql_token_view():
    view = GraphQLView.as_view(schema=schema)
    view = permission_classes((IsAuthenticated,))(view)
    view = authentication_classes((TokenAuthentication,))(view)
    view = api_view(['GET', 'POST'])(view)
    return view

urlpatterns = [
# ...
    url(r'^graphql_token', graphql_token_view()),
    url(r'^graphql', csrf_exempt(GraphQLView.as_view(schema=schema))),
    url(r'^graphiql', include('django_graphiql.urls')),
# ...

Note that I added a new ^graphql_token endpoint and kept the original ^graphql which is used by the GraphiQL tool.

Then, I set the Authorization header in my GraphQL client and point to the graphql_token endpoint.

I created this issue in case anyone else has the same question. Also, posted the question and answer to StackOverflow.

@syrusakbary
Copy link
Member

Thanks for sharing here!
Closing the issue as people would be able to get into here when searching in google "graphene token django rest framework" :)

@travisbloom
Copy link

travisbloom commented Feb 1, 2017

Adding some additional steps that I had to take when following this integration:

class RTGraphQLView(GraphQLView):

    def parse_body(self, request):
        if type(request) is rest_framework.request.Request:
            return request.data
        return super().parse_body(request)

Graphene was expecting the .body attr but DRF reads it and attaches it to .data before being passed to GraphQLView.

@jacobh
Copy link

jacobh commented May 9, 2017

For my use I rolled the two above examples up into a single view class:

from graphene_django.views import GraphQLView

import rest_framework
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import authentication_classes, permission_classes, api_view
from rest_framework.settings import api_settings


class DRFAuthenticatedGraphQLView(GraphQLView):
    def parse_body(self, request):
        if isinstance(request, rest_framework.request.Request):
            return request.data
        return super(APGraphQLView, self).parse_body(request)

    @classmethod
    def as_view(cls, *args, **kwargs):
        view = super(APGraphQLView, cls).as_view(*args, **kwargs)
        view = permission_classes((IsAuthenticated,))(view)
        view = authentication_classes(api_settings.DEFAULT_AUTHENTICATION_CLASSES)(view)
        view = api_view(['GET', 'POST'])(view)
        return view

@eamigo86
Copy link

Hi @jacobh I try to use your code but do not make anything, I can make requests without any authentication header.

@eamigo86
Copy link

Hi @jacobh. I already worked your code, the truth has saved me an important time. I did not work before because I had the private route after the public route, so I only had to define my private route before the public route and it worked perfectly

eamigo86 pushed a commit to eamigo86/graphene-django-extras that referenced this issue Oct 24, 2017
…anted model queryset.

Add AuthenticatedGraphQLView on graphene_django_extras.views for use
    'permission', 'authorization' and 'throttle' classes based on the DRF settings. Special thanks to
    [@jacobh](https://github.com/jacobh) for this [comment](graphql-python/graphene#249 (comment))
@dperetti
Copy link

@travisbloom It works but it's really a hack.
graphene.django.view.parse_body() is totally skipped, which should be fine in most situations, but it could also probably yield headaches in some edge cases.

@alshafai
Copy link

For my use I rolled the two above examples up into a single view class:

from graphene_django.views import GraphQLView

import rest_framework
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import authentication_classes, permission_classes, api_view
from rest_framework.settings import api_settings


class DRFAuthenticatedGraphQLView(GraphQLView):
    def parse_body(self, request):
        if isinstance(request, rest_framework.request.Request):
            return request.data
        return super(APGraphQLView, self).parse_body(request)

    @classmethod
    def as_view(cls, *args, **kwargs):
        view = super(APGraphQLView, cls).as_view(*args, **kwargs)
        view = permission_classes((IsAuthenticated,))(view)
        view = authentication_classes(api_settings.DEFAULT_AUTHENTICATION_CLASSES)(view)
        view = api_view(['GET', 'POST'])(view)
        return view

This implementation worked perfectly for me on my local development server. For some odd reason when I deploy it on AWS it gives me the following error :

You cannot access body after reading from request's data stream

@jstacoder
Copy link

@alshafai that is because you are calling super(APGraphQLView, self) when you should be calling super(DRFAuthenticatedGraphQLView, self)

@HideoKuze
Copy link

HideoKuze commented Feb 7, 2019

This was really great thank you. I want to include an additional line I added for people using GraphQL/Graphene with the Django OAuth Toolkit:

 from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, TokenHasScope, 
 OAuth2Authentication

 class DOTAuthenticatedGraphQLView(GraphQLView):
     def parse_body(self, request):
         if isinstance(request, rest_framework.request.Request):
             return request.data
         return super(DOTAuthenticatedGraphQLView, self).parse_body(request)

     @classmethod
     def as_view(cls, *args, **kwargs):
         view = super(DOTAuthenticatedGraphQLView, cls).as_view(*args, **kwargs)
         view = permission_classes((IsAuthenticated, TokenHasReadWriteScope, ))(view)  # add 
 permissions to the view
         view = authentication_classes((OAuth2Authentication,))(view)
         view = api_view(['POST'])(view)
         return view

Notice the view = authentication_classes((OAuth2Authentication,))(view) line, it's necessary for it to work. The previous line for the permission_classes isn't actually necessary it seems. But I kept it for whatever reason.

Thanks again, this has shown me I need to read deeper into what's going on with the views. Does anyone know any good articles that go into depth? I want to really learn more about these class methods so I can figure something like this out on my own in the future

@HideoKuze
Copy link

Adding some additional steps that I had to take when following this integration:

class RTGraphQLView(GraphQLView):

    def parse_body(self, request):
        if type(request) is rest_framework.request.Request:
            return request.data
        return super().parse_body(request)

Graphene was expecting the .body attr but DRF reads it and attaches it to .data before being passed to GraphQLView.

Can you explain to me exactly what happening here and why I need to do it? It works for me but I'm not certain as to why. Originally I was also getting the You cannot access body after reading from request's data stream error, but your code fixed it

@willianantunes
Copy link

Hi guys! I wrote a playground project with all you posted here.

The customized view:

https://github.com/willianantunes/django-graphql-playground/blob/151b59c29c648ce053937ea70ad07af0d7db74d9/api/graphql/views.py#L10

Its setup:

https://github.com/willianantunes/django-graphql-playground/blob/151b59c29c648ce053937ea70ad07af0d7db74d9/django_graphql_playground/urls.py#L29

And some integration tests with GraphQL clients:

Thank you!

@Maushundb
Copy link

We really should get this added to the official docs

@Booshra
Copy link

Booshra commented Mar 23, 2020

Could anyone tell me where do I get the token returned? I could not retrieve it in my mutation
I have user serialized mutation, and I am trying to retrieve the token at graphql endpoint using the user as key but it is saying, "Token matching query does not exist"

@jstacoder
Copy link

You need to create the token first

@cutamar
Copy link

cutamar commented Mar 26, 2020

Personally, I use https://django-graphql-jwt.domake.io/en/latest/quickstart.html
It allows you to make multiple queries in one request authenticated as different users, and many more features.

@Booshra
Copy link

Booshra commented Mar 26, 2020

@cutamar I used django-graphql-jwt for my login by calling the mutation as following.
token_auth = graphql_jwt.ObtainJSONWebToken.Field()
but I don't know how to use it for registration.
I wanted to generate a token while the user create mutation is called, just like how this token_auth mutation returns a token.
Could you please show how you used graphql-jwt for registration, thanks.

@Booshra
Copy link

Booshra commented Mar 26, 2020

@jstacoder i did generate the token, but when i pass that token as header in my login query, it doesn't work. prompts: could not match signature
This is my login query:

user_login = graphene.Field(UserType)
    def resolve_user_login(self, info):
        # returns user profile info when authenticated
        user = info.context.user
        if user.is_anonymous:
            raise Exception('Must Log In!')

        return user

@cutamar
Copy link

cutamar commented Mar 30, 2020

@Booshra I created a ModelSerializer for my custom User model. In graphene I use a SerializerMutation for this serializer, this way I can create and update users. Just note to NOT use the @login_required decorator of django-graphql-jwt on the create endpoint.

@jstacoder
Copy link

jstacoder commented Apr 3, 2020

@Booshra did you pass it like the graphql-jwt docs say to? ie: Authorization: JWT <token>

so preface the token with: JWT

@Booshra
Copy link

Booshra commented Apr 4, 2020

@jstacoder yes I did do that.
I solved my problem using a shortcut from https://github.com/flavors/django-graphql-jwt/blob/master/docs/authentication.rst#http-header
You can have a look at it:
graphql-jwt https://github.com/flavors/django-graphql-jwt/blob/master/graphql_jwt/shortcuts.py
My mutation class looks like this now

class UserCreateMutation(graphene.Mutation):
    user = graphene.Field(UserType)
    otp_status = graphene.String()
    token = graphene.String()
    # token_ok = graphene.Boolean()
    refresh_token = graphene.String()
​
    class Arguments:
        password = graphene.String(required=True)
        email = graphene.String(required=True)
        mobile_no = graphene.String(required=True)
        first_name = graphene.String(required=True)
        last_name = graphene.String(required=True)
        time = graphene.String()
​
    def mutate(self, info, password, email, mobile_no, first_name, last_name, time):
        user = get_user_model()(
            username=str(uuid4())[0:10],
            email=email,
            mobile_no=mobile_no,
            first_name=first_name,
            last_name=last_name,
            created_on=timezone.now(),
            modified_on=timezone.now(),
            code_valid_till=timezone.now() + timedelta(minutes=5),
            verification_code=randint(100000, 999999)
        )
        user.set_password(password)
        user.timezone = time
        user.save()
        token = graphql_jwt.shortcuts.get_token(user)
        refresh_token = graphql_jwt.shortcuts.create_refresh_token(user)
        otp_code = user.verification_code
        otp_flag = send_sms_otp(user.mobile_no, otp_text.format(
            otp_code))
        otp_status = "OTP send successfully" if otp_flag else "OTP failed"
        return UserCreateMutation(user=user,
                                  token=token,
                                  refresh_token=refresh_token,
                                  otp_status=otp_status)

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

No branches or pull requests