I am running Mongoose and exposing an API using GraphQL (Apollo).
I want to implement a RBAC and after some research I came with a solution using CASL and graphql-shield. Ideally, I would then want to share the rules with my React front-end.
First step, planning on a piece of paper.
I would first define my actions: Create, Read, Update, Delete.
Then I would define my subjects: Car, Motorcycle.
After that is done I would proceed to define my roles: CarSpecialist, MotoSpecialist, Admin. I would then define some conditions: "subject is my own", etc..
Finally, I would assign to each role, a set of abilities (combination of action, subject, conditions).
Now with all this done, I start actually coding my solution.
I start by writing the abilities in CASL: actions and subjects are pretty straightforward to define.
Conditions are a bit trickier and I have at least two options:
I use "vague" notions that in turn have to be interpreted by whatever needs to enforce them (back or front end).
I use the CASL mongoose integration plugin, at the cost of losing the ability to share with my frontend.
Any input on which to choose?
Now once CASL abilities are defined, is it up to graphql-shield to enforce them?
How do I do the mapping between (CASL) actions, subjects and conditions to graphql terms: Schema, Query, Mutations ...?
I’ll try to answer on this question as much as I can:
ability.can
. So, if mongo query language is fine for you, then no need to change it!ownerId
of Car) as part of graphql type. But if the only purpose of this is to satisfy permissions sharing, then I’d go with transformation option:function defineAbility(user, props) {
const { can, rules} = new AbilityBuilder(Ability)
can('read', 'Post', { [props.authorId]: user.id })
// ...
return rules;
}
const currentUser = { id: 1 }
const backendRules = defineAbility(currentUser, {
authorId: 'authorId'
});
const uiRules = defineAbility(currentUser, {
authorId: 'author.id'
});
Alternatively, you can check permissions on backend and share results with frontend by exposing subtype on every graphql type:
{
cars {
items {
permissions {
canUpdate
canRead
}
}
}
}
The consequences of this is that your server will spend more time generating response, especially when you retrieve items for pagination. So, check response times before proceeding with that. The good point of this is that you don’t need casl on UI, and permissions checking logic is completely hidden on backend