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.
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" :)
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.
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
Hi @jacobh I try to use your code but do not make anything, I can make requests without any authentication header.
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
@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.
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
@alshafai that is because you are calling super(APGraphQLView, self) when you should be calling super(DRFAuthenticatedGraphQLView, self)
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
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
.bodyattr but DRF reads it and attaches it to.databefore 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
Hi guys! I wrote a playground project with all you posted here.
The customized view:
Its setup:
And some integration tests with GraphQL clients:
Thank you!
We really should get this added to the official docs
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"
You need to create the token first
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.
@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.
@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
````
@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.
@Booshra did you pass it like the graphql-jwt docs say to? ie: Authorization: JWT <token>
so preface the token with: JWT
@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)
Most helpful comment
For my use I rolled the two above examples up into a single view class: