Description
Authorization in GraphQL is (as noted by @helfer in this post) currently not a generally solved problem. Everybody has their own, custom solution that works for them but there's no general-purpose solution/library for it. (as far as I can tell)
I think graphql-tools
is uniquely positioned to fill that hole in the community. Permissions are naturally bound to the schema of the requested data, so to me it feels like a natural fit for graphql-tools
. (note that this is just an idea, wanted to kick off a discussion)
Imagine a simple type like this:
# I want posts to only be readable by logged-in users
type Post {
id: ID
content: String
author: User
notes: [String] # I want these to only be accessible by the author
}
extends type Query {
post(id: ID!): Post
}
There's two types of permissions I want to enforce here. One of them I'm able to tell without running the resolvers first (only readable by the logged-in user), for the other I need the data from the database so they have to run after the resolvers. (only accessible by the author)
I could imagine an API like this working out well:
makeExecutableSchema({
schema: schema,
resolvers: resolverMap,
// This is the new part:
permissions: permissionMap,
})
Similar to resolvers
this permissionMap
would map to the schema:
const permissionMap = {
Query: {
post: () => { /* ... */ }
},
Post: {
notes: () => { /* ... */ }
}
}
Depending on if you return a truthy or a falsey value from these functions folks get access or a "Permission denied" error. The functions get the same information as resolvers. (i.e. obj
, args
and context
)
This works for the pre-database case (only logged-in users should be able to access), since I could do something like this:
// The user comes from the context, and is passed in from the server
// Note: This is simplified, there's more complex checks to figure out if somebody is logged in of course
const isLoggedIn = (_, _, { user }) => user;
const permissionMap = {
Query: {
post: isLoggedIn
},
Post: isLoggedIn
}
Now the issue is post resolver permission functions. I don't have a good idea how to handle these right now, but maybe something like this could work where each "path" has two magic properties (.pre
, .post
) which run those functions before and after the resolver:
const isLoggedIn = (_, _, { user }) => user;
// Check if the user requesting the resource is the author of the post
const isAuthor = (resolverResult, _, { user }) => user.id === resolverResult.author;
const permissionMap = {
Query: {
post: isLoggedIn
},
Post: {
'.pre': isLoggedIn,
notes: {
'.post': isAuthor,
}
}
}
What do you think? Any previous discussions I'm missing?