Using mercurius you can optionally filter the GraphQL schema based on the user's permissions. This feature limits the Introspection queries visibility. Doing so, the user will only be able to see the fields that are accessible to them.
To enable this feature, you can use the filterSchema plugin's option:
const Fastify = require('fastify')
const mercurius = require('mercurius')
const mercuriusAuth = require('mercurius-auth')
const schema = `
directive @hasPermission (grant: String!) on OBJECT | FIELD_DEFINITION
type Message {
title: String!
message: String!
notes: String @hasPermission(grant: "see-all")
}
type Query {
publicMessages: [Message!]
}
`
const app = Fastify()
app.register(mercurius, {
schema,
resolvers
})
app.register(mercuriusAuth, {
filterSchema: true,
authDirective: 'hasPermission',
authContext: function (context) {
return { permission: context.reply.request.headers['x-permission'] }
},
applyPolicy: async function hasPermissionPolicy (authDirectiveAST, parent, args, context, info) {
const needed = authDirectiveAST.arguments.find(arg => arg.name.value === 'grant').value.value
const hasGrant = context.auth.permission === needed
if (!hasGrant) {
throw new Error(`Needed ${needed} grant`)
}
return true
}
})
app.listen({ port: 3000 })After starting the server, you can use the following GraphQL query to test the filtering:
{
__type (name:"Message") {
name
fields {
name
}
}
}You should get the following response:
{
"data": {
"__type": {
"name": "Message",
"fields": [
{
"name": "title"
},
{
"name": "message"
}
]
}
}
}The notes field is not accessible to the user because the user doesn't have the see-all permission.
Adding the Request Headers as follows:
{
"x-permission": "see-all"
}Will make the user able to see the notes field.
- During the introspection query, the
applyPolicyfunction is executed once per single GraphQL object. - The
applyPolicyfunction doesn't have the inputparentandargsarguments set during the introspection run. - The
applyPolicyfunction receives theinfoargument which contains basic information about the GraphQL entity assinged to the directive. - When the HTTP request payload contains an introspection query and a user-land query, you will not get auth errors because the introspection query is executed before the user-land query and filters the schema. Note that the protected fields will not be returned as expected, without any security implications. Here is an example of a GraphQL query that will not throw an error:
{
__type (name:"Message") {
name
fields {
name
}
}
publicMessages {
notes
}
}Depending on the DirectiveLocations you will have two main behaviors:
- The normal behaviour is when the
DirectiveLocationsis hidden from the introspection query. - The augmented behaviour is when the schema has additional hidden entities from the introspection query. It happens when:
- The directive is
on INPUT_FIELD_DEFINITION: the wholeinputitem is hidden - The
Queryis hidden if the user doesn't have access to theinputoroutputobject types.
- The directive is