Closed Oxyrus closed 6 years ago
@Oxyrus you can access request and its headers by using context field from info parameter in your resolve methods:
def resolve_some_field(self, info, ...):
auth_header = info.context.META.get('HTTP_AUTHORIZATION')
However, for authentication purposes, you'd have to do that for all of your queries and mutations. Instead of that, you can define a custom view for your /graphql
endpoint like this:
class TokenAuthGraphQLView(GraphQLView):
def dispatch(self, request, *args, **kwargs):
auth_header = request.META.get('HTTP_AUTHORIZATION')
if valid_header(auth_header):
return super().dispatch(request, *args, **kwargs)
else:
return HttpResponse('Authorization Error', status=401)
And use this view in your urls.py. You need to define valid_header
function that will do actual validation.
Let me know if this helps.
Thanks a lot @abaskov it definitely helps!
One question, doesn't your view require all the requests to be authenticated? What if you want to expose only mutations for logged in users but queries available for anyone (logged or non logged users)?
@Oxyrus that's correct.
In this case, you can implement your own Middleware (http://docs.graphene-python.org/en/latest/execution/middleware/) and have different rules for different fields in your schema.
Other option would be to have two endpoints. One for non-logged users served by GraphQLView with query containing fields that should be exposed to everyone. And another endpoint served by TokenAuthGraphQLView with queries and mutations available to logged in users. I like it as it makes it easier to distinguish between public and private APIs, but GraphQL specification recommends having only one endpoint, so it might not follow the specification fully.
The third option would be to authenticate non-logged users as guests and provide access to certain fields based on your authorization rules and framework, but that's basically the same as having authentication checks in all fields.
@abaskov Thanks again!
So if I understand correctly, it's possible to define a middleware as a function and then use it in different fields, is that correct?
The piece I don't understand in the docs is this one
result = schema.execute('THE QUERY', middleware=[AuthorizationMiddleware()])
Where should I place result? It almost feels like it should be in my root schema, but I just want to have a middleware check whether the request contains a valid JWT or not for certain fields in my schema, not for every single one.
Thanks once again 💯
@Oxyrus you can check the field name inside your Middleware and perform necessary auth checks if access to the field should be limited. Slightly modified example from docs:
class AuthorizationMiddleware(object):
def resolve(self, next, root, info, **args):
if info.field_name == 'user':
auth_header = info.context.META.get('HTTP_AUTHORIZATION')
if valid_header(auth_header):
return None
return next(root, info, **args)
Though I haven't used middleware for this purposes so can't say for sure if that's the right way.
@Oxyrus
I think the Middleware is a great way to get this sort of functionality without any changes to graphene-django.
From what I can tell, you should be able to check if info.operation == 'mutation':
to require authentication only on mutations.
Hopefully in the not too distant future, we will have a permissions framework that integrates Django Permissions at several levels in the schema, especially on mutations and object types, but hopefully also as granular as specific fields/resolvers. When that functionality arrives, it will be even clearer and hopefully more flexible to achieve the functionality you want.
Let us know if you get your problem solved in the meantime.
Thanks!
It does, thank you so much @abaskov & @spockNinja 👍
@Oxyrus how did you a solve this
result = schema.execute('THE QUERY', middleware=[AuthorizationMiddleware()])
In case you have Django REST Framework installed, you can add a simple Django middleware to your settings to support token based authentication on all requests, including the GraphQL queries:
from rest_framework.authentication import TokenAuthentication
class TokenAuthMiddleware:
def __init__(self, get_response):
self.auth = TokenAuthentication()
self.get_response = get_response
def __call__(self, request):
if request.META.get("HTTP_AUTHORIZATION") and not request.user.is_authenticated:
user = self.auth.authenticate(request)[0]
request.user = user
return self.get_response(request)
Just add this to your Django application's MIDDLEWARE
settings and refer to the Django REST Framework documentation on how the tokens are handled otherwise.
Just to clarify you should add your middleware to GRAPHENE in settings. I would love if the docs could be more clear on where to add middleware. Here is what I am going with for now.
GRAPHENE = {
'SCHEMA': 'my_app.schema.schema'
'MIDDLEWARE': (
'path.to.DRFAuthorizationMiddleware',
)
}
from rest_framework.authtoken.models import Token
class DRFAuthorizationMiddleware(object):
'''
This piece of middleware adds the DRF user to each graphql resolver's context
'''
def __init__(self):
pass
def resolve(self, next, root, info, **args):
auth_header = info.context.META.get("HTTP_AUTHORIZATION")
if auth_header:
token = auth_header.split('Bearer ')[1]
user = Token.objects.get(key=token).user
info.context.user = user;
return next(root, info, **args)
Something also worth mentioning is that if you're running your GraphQL endpoint on a view that's CSRF exempt (which you probably are), you should make sure to not trust session based authentication at all. If you don't, your API may be CSRF vulnerable for example if you're still using the Django admin site, which still uses session based authentication.
So simply put, make sure the request has been authenticated specifically by your token authentication (or some other non-csrf vulnerable authentication).
Note when implementing the django-graphene
middleware. In my case when I try to implement the following codes:
class AuthGraphQLMiddleware(object):
def resolve(self, next, root, info, **args):
print(root)
return next(root, info, **args)
It seems like it prints multiple times based on how many resolvers you have in your schema especially when you are loading the GraphQLView
.
So to place your logic there where you check user credentials (e.g. access token) for instance is inefficient and not recommended. Even when requesting a simple query/mutation command via postman it will call the middleware multiple times based on the request-response cycle.
Note when implementing the
django-graphene
middleware. In my case when I try to implement the following codes:class AuthGraphQLMiddleware(object): def resolve(self, next, root, info, **args): print(root) return next(root, info, **args)
It seems like it prints multiple times based on how many resolvers you have in your schema especially when you are loading the
GraphQLView
.So to place your logic there where you check user credentials (e.g. access token) for instance is inefficient and not recommended. Even when requesting a simple query/mutation command via postman it will call the middleware multiple times based on the request-response cycle.
Graphene middleware is executed for every field, not just per-request like Django's
Hello!
First of all, thank you so much for making Graphene and Graphene Django, they're incredible.
Currently I'm trying to implement an authentication system, my idea was to return a JWT on a successful login and then send the token in a
Authorization
header, however, I don't have any idea on how to access the request headers (or even if it's possible).Thank you so much once again 👏