graphql-python / graphene-django

Build powerful, efficient, and flexible GraphQL APIs with seamless Django integration.
http://docs.graphene-python.org/projects/django/en/latest/
MIT License
4.31k stars 770 forks source link

Is it possible to get the headers from the request? #345

Closed Oxyrus closed 6 years ago

Oxyrus commented 6 years ago

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 👏

abaskov commented 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.

Oxyrus commented 6 years ago

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)?

abaskov commented 6 years ago

@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.

Oxyrus commented 6 years ago

@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 💯

abaskov commented 6 years ago

@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.

spockNinja commented 6 years ago

@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!

Oxyrus commented 6 years ago

It does, thank you so much @abaskov & @spockNinja 👍

marvinkome commented 6 years ago

@Oxyrus how did you a solve this result = schema.execute('THE QUERY', middleware=[AuthorizationMiddleware()])

JoaRiski commented 4 years ago

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.

jottenlips commented 4 years ago

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)
JoaRiski commented 4 years ago

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).

roelzkie15 commented 3 years ago

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.

lovetoburnswhen commented 3 years ago

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