graphql-python / flask-graphql

Adds GraphQL support to your Flask application.
MIT License
1.32k stars 140 forks source link

Best way to authenticate requests #17

Open jwfehr opened 8 years ago

jwfehr commented 8 years ago

I am trying to figure out the best way to authenticate a request before my schema executes the query/mutation. I was thinking I could use the @app.resolver('url') tag and create a function that would authenticate the request and then pass it to the graphqlview. Something like this:

@app.route('/graphql/')
def graphql():
    print request.headers #AUTHENTICATE HERE
    print request.data
    return GraphQLView.as_view('/graphql/', schema=schema, graphiql=True)

But this doesn't work, can someone either show me what I am doing wrong here or give me a different way to authenticate the request? Thanks!

kmakihara commented 7 years ago

I had a similar problem, but I basically implemented a workaround such that a user could access the graphqlview only on the testing/development environment (localhost). Depends on what your goal is, I only had the graphqlview to help me model and test graphql queries, and didn't actually want it available in the production environment. Here's the code if you're interested:

   isLocal = 'FLASK_DEBUG' in os.environ and os.environ['FLASK_DEBUG'] == '1'
   app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, context={ 'client': MONGO_CLIENT }, graphiql= True if isLocal else False))
tylfin commented 7 years ago

@kmakihara Did you end up writing individual graphql routes that handle authentication? How are you limiting the amount of data an authenticated user can potentially receive without limiting the usefulness of graphql?

waxisien commented 7 years ago

It's been a while that issue is open, but for general purpose, I ended up using route decorators in my view function like this :

from flask_jwt_extended import jwt_required
...

def graphql_view():
    view = GraphQLView.as_view('graphql', schema=schema, context={'session': db.session},
                               graphiql=True)
    return jwt_required(view)

app.add_url_rule(
    '/graphql',
    view_func=graphql_view()
)

Here I use a JWT authentication with but I could use flask-login or any authentication method:

from flask_login import login_required
...

def graphql_view():
    view = GraphQLView.as_view('graphql', schema=schema, context={'session': db.session},
                               graphiql=True)
    return login_required(view)
...
microidea commented 6 years ago

Here is maybe a more intuitive way for those who don't use jwt:

def auth_required(fn):
    def wrapper(*args, **kwargs):
        session = request.headers.get(AUTH_HEADER, '')
        # Do some authentication here maybe...
        return fn(*args, **kwargs)
    return wrapper

def graphql_view():
    view = GraphQLView.as_view(
        'graphql',
        schema=schema,
        graphiql=True,
        context={
            'session': DBSession,
        }
    )
    return auth_required(view)

app = Flask(__name__)
app.debug = True
app.add_url_rule(
    '/graphql',
    view_func=graphql_view()
)
rdhara commented 6 years ago

Taking the suggestions above, I have been able to use existing Python JWT libraries to authenticate the /graphql endpoint. However, I am unsure what the best practice is to handle user identity management, which involves decoding the token and reading off the identifying information. For instance, if I have query{portfolio}, I want to be able to return the portfolio of the authenticated user. I am not sure how to get that information and pass it along to the schema so that it can be used in resolver and mutator functions. Right now, I feel I am doing something rather silly: I have an additional endpoint in my Flask app called /jwt_id that accepts a JWT parameter in the payload, which I then decode and return the id. Then in all of the resolvers, I use requests to hit this endpoint, which is completely redundant since now the token is in the payload and in the header but I couldn't think of a workaround. Is there a clean way to pass the decoded token information to the schema? I'm assuming I can somehow leverage the context parameter...?

UPDATE: Figured it out - context is indeed the way to do it!

comtihon commented 6 years ago

I am using flask + jwt. For queries I just use Viewer, as a top level object:

class Query(graphene.ObjectType):
    ....
    viewer = graphene.Field(Viewer)

    @staticmethod
    def resolve_viewer(_root, info):
        return Viewer.get_user_by_token(info.context.headers.get('Authorization'))

For mutations I have to create abstract mutation class and extend it with all my mutation except login mutation:

class AuthorizedMutation(relay.ClientIDMutation):
    class Meta:
        abstract = True

    @classmethod
    @abstractmethod
    def mutate_authorized(cls, root, info, **kwargs):
        pass

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        _ = auth_service.authorize_token(info.context.headers.get('Authorization'))
        return cls.mutate_authorized(root, info, **kwargs)

All credentials and tokes related work is handeled by auth_service. Viewer.get_user_by_token also calls it.

Hope it will be helpful. P.S.: Do not forget, that subscriptions also need to be secured.

rscarrera27 commented 6 years ago

Hi, I'm the maintainer of Flask-GraphQL-Auth. Inspired by Flask-JWT-Extended, There is a problem with error-handling but it works pretty well. How about try this?

You can use Flask-GraphQL-Auth like you used Flask-JWT-Extended.

here are some examples.

class Query(graphene.ObjectType):
    protected = graphene.String(message=graphene.String(),
                                token=graphene.String())

    @query_jwt_required
    def resolve_protected(self, info, message):
        return str(get_raw_jwt())

you can find more on github and docs

GitHub: https://github.com/callsign-viper/Flask-GraphQL-Auth Docs: https://flask-graphql-auth.readthedocs.io/en/latest/