-
Couple of things:
// scope loader with argument
customPerm: (perm) => context.permissionService.hasPermission(context.User, perm),
authScopes: (article, args, context, info) => {
if (context.User.id === article.author.id) {
// If user is author, let them see it
// returning a boolean lets you set auth without specifying other scopes to check
return true;
}
// If the user is not the author, require the employee scope
return {
employee: true,
};
},
grantScopes: (parent, args, context, info) => ['readArticle'], Especially when coupled with CASL, I am a little confused with what's the best method to use when using an abilities library. What are the pros/cons of each and when should each of those methods be used? I want to say
|
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
Auth is a complicated topic, lots of opinions, strategies and tradeoffs that can be made.
CASLHere is an example using a couple of the things described above. This is a rough idea based on a brief look at the CASL docs. Setup:
function createContext() {
const user = getLoggedInUser();
const ability = defineAbilitiesFor(user);
return {
user,
ability
}
}
const builder = new SchemaBuilder<{
Context: {
user: User,
ability: Ability,
};
AuthScopes: {
casl: [action: string, subject: unknown],
};
}>({
plugins: [ScopeAuthPlugin],
authScopes: async (context) => ({
casl: ([action, subject]) => context.ability.can(action, resource),
}),
}); Auth for a field: builder.queryField('example', (t) => t.string({
authScopes: { casl: ['someAction', 'SomeSubject'] },
resolve: () => 'hi',
})); Auth for a type (applies to all fields) builder.objectType('Thing', {
authScopes: {
casl: ['someAction', 'SomeSubject'],
},
fields: () => ({}),
}); Auth for field that uses the parent (instance of subject): builder.objectField('ExampleType', 'exampleField', (t) => t.string({
authScopes: (parent) => ({ casl: ['someAction', parent] }),
resolve: () => 'hi',
})); Auth for a type using parent (applies to all fields) builder.objectType('Thing', {
authScopes: parent => ({
casl: ['someAction', parent],
}),
fields: () => ({}),
}); I put these descriptions and examples together pretty quickly, so there are probably lots of typos, and few other mistakes, but hopefully the general explanations make sense. For future questions like this, I think github discussions might be a better fit than issues. I'd like to track common questions like this there so they are easier for others to learn from without having a bunch of open issues. |
Beta Was this translation helpful? Give feedback.
-
To elaborate a bit on the CASL example above, I would probably use something like ['read', 'SomeResource'] on the 'SomeResource' type, and then add additional field level scopes for mutations like ['create', 'SomeResource']. |
Beta Was this translation helpful? Give feedback.
-
Oh nice, this is an awesome example of using CASL! As for return type, I assume the best thing to do here is builder.queryField('example', (t) => t.string({
authScopes: { casl: ['someAction', 'SomeSubject'] },
resolve: (_p, _a, { ability }) => {
const example = /* find example */
ForbiddenError.from(ability).throwUnlessCan('read', example);
return example;
}
})); |
Beta Was this translation helpful? Give feedback.
Auth is a complicated topic, lots of opinions, strategies and tradeoffs that can be made.
customPerm
): This pattern is useful for integrating with other auth libraries or permissions systems. It allows you to have a place on fields and types where you can define a parameters that will be passed to another library. This is what I would recommend for something like CASL (I'll add more detail below), but it is very flexable and can be used in a number of different ways.authScopes
as a function on a field: This is useful if the scopes needed to resolve a f…