Apollo-server: Hapi: Authentication per resolver

Created on 25 Jun 2017  路  4Comments  路  Source: apollographql/apollo-server

I have not found any working example/docs to use authentication per resolver on the server side with hapi. It's a bit impractical to set the authentication for all graphql requests as some resolvers should provide a different chunks based on users authorisation.

I'm using an httponly cookie based authentication and this is hapi config for graphql:

{
  register: graphqlHapi,
  options: {
    route: {
      auth: {
        strategy: 'session',
        mode: 'try'
      }
    },
    graphqlOptions: {
      schema
    }
  }
};

There is a validation function from hapi's authentication strategy which will populate request.auth object with necessary auth data. An example of a route handler and auth object looks like this:

server.route({
  method: 'GET',
  path: '/protected-rest-path',
  handler: (request, reply) => {
    console.log(request.auth); // the whole auth object
  }
});
// A sample of request.auth object:
{ 
  isAuthenticated: true,
  credentials: {
    username: 'name',
    description: 'credentials object is populated by validateFunc from an auth strategy, e.g. user authorisation, user id, ...',
  },
  artifacts: {
    token: 'l1oiYKda8vVhN2+N6S/rdkG5cv33gF6+'
  },
  strategy: 'session',
  mode: 'try',
  error: null
}

I found that Apollo GraphQL handler is passing request further:
https://github.com/apollographql/graphql-server/blob/master/packages/graphql-server-hapi/src/hapiApollo.ts#L62

However, it's lost in runHttpQueryWrapper function which does not do anything with request.auth:
https://github.com/apollographql/graphql-server/blob/master/packages/graphql-server-hapi/src/hapiApollo.ts#L22-L26

I was looking a bit further in the call stack but it seems that the concept of passing data does not consider any auth object or anything except variables (for graphql) and query (from a client):
https://github.com/apollographql/graphql-server/blob/master/packages/graphql-server-core/src/runHttpQuery.ts#L108-L120

My thinking is, that I'd like to get additional props per query object or skip database update on mutation if authorisation does not match. I found a suggestion to use a middleware but that seems a bit clumsy to control an authorisation between a single endpoint and many resolvers. It would also need a different resolver to achieve the first case with additional attributes.

Have I overlooked a place which could help me to use auth per resolver? Would anyone have an insight if it's even possible to add such a thing as it seems that params object from the runHttpQuery is generic for all server implementations? ... or, would be possible to pass hapi request object into a resolver function?

Most helpful comment

@quirm manually putting the request on the context if you need it is exactly what I would recommend. It's better to pass things explicitly, because that keeps things neatly separated.

All 4 comments

Digging a bit further, would you accept a PR which is passing request as a part of context prop? A modified hapi handler would look like this:

handler: function (request, reply) {
  options.graphqlOptions.context = options.graphqlOptions.context || {};
  Object.assign(options.graphqlOptions.context, { request });
  return runHttpQueryWrapper(options.graphqlOptions, request, reply);
}

I would prefer the whole request in case there are other properties which could be accessed but are filtered by runHttpQueryWrapper.

Hi, thanks for this great library.
I am running into the same issue here, and passing the whole request object would definitely be better.

Context prop is actually a suggested approach for such a thing as mentioned by Dan Schafer at https://youtu.be/etax3aEe2dA?t=989.

There was a change to pass a request by defining graphqlOptions as a function.

From this:

module.exports.graphql = {
  register: graphqlHapi,
  options: {
    route: {
      auth: {
        strategy: 'session',
        mode: 'try'
      }
    },
    graphqlOptions: {
      schema
    }
  }
};

I can use this:

module.exports.graphql = {
  register: graphqlHapi,
  options: {
    route: {
      auth: {
        strategy: 'session',
        mode: 'try'
      }
    },
    graphqlOptions: request => ({
      schema,
      context: request // hapi request
    })
  }
};

It seems unusual to temper with GraphQLOptions just to manually enhance it by a request to get it in a resolver function. That needs some knowledge of the internal behaviour. Could we possibly automate it and just expand context object by the corresponding request?

Any thoughts @NeoPhi, @helfer ? @NeoPhi do you have a different usage in your project except for passing request as context prop?

@quirm manually putting the request on the context if you need it is exactly what I would recommend. It's better to pass things explicitly, because that keeps things neatly separated.

Was this page helpful?
0 / 5 - 0 ratings