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