3 ms·
The permissions checking is one-by-one checks. It's exactly as hard a mistake to make in GraphQL as it is in REST unless you've got more resolvers than an equiv
by strken 2y ago
The permissions checking is one-by-one checks. It's exactly as hard a mistake to make in GraphQL as it is in REST unless you've got more resolvers than an equivalent REST app would have, which is unlikely and would mean GraphQL wasn't a good choice.
I do think that you've got a good point about how the knowledge isn't widespread yet, that it's easier for frontend engineers to write awful expensive queries, and that GraphQL is very hard to secure against DoS unless you lock it down with query hashes.
- mewpmewp2 2y agoWouldn't it seem contra to the principles of GraphQL if you treat resolvers like rest endpoints? At this point, it's just RPC, no? It's not really a graph. Why didn't I just use RPC/Rest the whole time?
- strken 2y agoYou don't treat resolvers like RESTful endpoints. You check that the user has permission to access the object (edit: or other value) which the resolver returns. This has nothing to do with RPC and does not stop you using the "graph" part of GraphQL. For the purposes of comparing a REST API, where permissions checking is done for every endpoint, to a GraphQL API, where permissions checking is done for any resolver which loads data, it is necessary to compare the number of permissions checks you would need across the two services. This does not mean resolvers are in any way equivalent to RESTful endpoints except for comparing how many times you'd need to write `ctx.can('read', photo);` across the two, and even then the numbers will almost certainly be different because the APIs will be different.
- yawaramin 2y agoThe problem is the 'graph' nature of the system; you can check the permission for the object that the resolver returns but that object might be linked to another object that you're not checking for. Because anything can just link to anything, you would have to recursively check the permissions of the entire graph.
- strken 2y agoThis does not match my experience. If the root query lets you query a user of type User, and the User object embeds an array photos of type [Photo], then there are two possibilities: either the resolver for user is loading the photos and letting the default resolver return them, in which case you know about it and can check permissions for them, or there's a resolver defined for photos, in which case you can check permissions in that second resolver. Think about it. GraphQL won't go retrieve rows from your database without either a) you installing some other library to do the magic, in which case we should talk about that library instead, or b) you telling it to query your database, in which case you know what data you're querying in each resolver you write and can check that the user has permission to see it.
- consteval 2y ago> The permissions checking is one-by-one checks Not true, authorization can be done in middleware. You can deny requests automatically, even scenarios you never considered.
- strken 2y agoI was responding to someone who was talking about one-by-one checks in REST. It is in fact true that using one-by-one checks in GraphQL is pretty similar to using them in REST. You can do the equivalent of applying middleware at a routing level in GraphQL by wrapping multiple resolvers, although the semantics will be different because you're not working with a tree of routes and so you'll need to group your resolvers together in some other way. In the Node.js libraries a resolver is just a function, so you can very easily wrap a bunch of them in another function: // auth.js export const checkParent = (permission, fn) => (parent, args, ctx) => { ctx.can(permission, parent); // CASL return fn(parent, args, ctx); }; // resolvers.js import * as auth from './auth'; export const resolvers = { // could also iterate over all the resolvers within User using Object.entries and apply auth.checkParent if you wanted User: { photoURLs: auth.checkParent('read', (parent, _, ctx) => { return parent.getSignedPhotoURLs(); }), }, Query: { user: async (_, { id }, ctx) => { const user = await ctx.db.users.getById(id); ctx.can('read', user); return user; }, }, }; I'm not sure what you mean by "deny requests automatically" because there's obviously no manual step here, and equally obviously I'm not sure what you mean by "scenarios [I] never considered". Are you talking about rate limiting or heuristic detection? You can do those in GraphQL too. Yes, this stuff is slightly different, but it's genuinely not that hard to secure a GraphQL API.