Still I think this type of thing is much more likely to happen with GraphQL including various N + 1 and even worse performance issues.
Like if you imagine having junior engs they will be much more likely to make the mistake with GraphQL than otherwise and it is harder to review as well.
The permissions checking becomes a real spaghetti and difficult to understand in practice compared to just one by one checks.
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.
I 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.
You 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.
The 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.
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.
Like if you imagine having junior engs they will be much more likely to make the mistake with GraphQL than otherwise and it is harder to review as well.
The permissions checking becomes a real spaghetti and difficult to understand in practice compared to just one by one checks.