Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.
06-24-2020 06:52 PM
Hi according to the source above, when the @hasScope directive has been enabled, the auto-generated queries and mutations will require valid JWT which with the necessary scope claim.
Since neo4j-graphql.js automatically adds Query and Mutation types to the schema, these auto-generated fields cannot be annotated by the user with directives. To enable authorization on the auto-generated queries and mutations, simply enable the
hasScope
directive and it will be added to the generated CRUD API with the appropriate scope for each operation
How do I figure out which scope has been added for each operation? Every time, I run a query it simply returns a 'You are not authorized for this resource' error message.
For example, I tried adding the scope 'User:Read' to the JWT that my server generates. However this does not authorize a user to query the User Object type. See example below:
.
06-25-2020 07:29 AM
Do you have a copy of your schema? How are you applying the directive?
07-04-2020 06:00 PM
I am stuck in the same spot. I manage to query when I have a simple @isAuthenticated directive on types, and when I generate my schema with:
const schema = graphql.makeAugmentedSchema({
typeDefs,
mutations: true,
config: {
auth: {
isAuthenticated: true,
hasRole: true,
},
},
});
If I generate my schema with
const schema = graphql.makeAugmentedSchema({
typeDefs,
mutations: true,
config: {
auth: {
isAuthenticated: true,
hasRole: true,
hasScope: true,
},
},
// schemaDirectives: { hasScope: MyHasScopeDirective },
});
then I cannot figure out for the life of me what scopes I need to add to my token. I have tried many combinations e.g. "Read:User", "User:Read", "User:Query", etc. to no avail. The doc doesn't say what scopes are required so I am shooting in the dark.
07-04-2020 06:17 PM
My workaround was to specify custom scopes in type definitions.
// For example:
type Query {
User: [User] @hasScope(scopes:["User:read"])
}
Then I add the scope into my JWT payload..
07-06-2020 07:03 PM
^ good workaround
I ended up writing a custom directive:
const schema = graphql.makeAugmentedSchema({
typeDefs,
mutations: true,
config: {
auth: {
isAuthenticated: true,
hasRole: true,
hasScope: true,
},
},
schemaDirectives: { hasScope: MyHasScopeDirective },
});
My directive printed out the requested scope. Turns out the generated scopes have a space between the node label and the operation, e.g. "Actor: Read".
I also ended up keeping this custom directive and handled authorizations there because it become tedious to add so many scopes to my JWT token. So instead, I rely on roles more, and use scopes as needed.
07-19-2020 10:36 PM
Any chance you could post your custom directive? I'm struggling to make mine
07-20-2020 06:30 AM
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly)
symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(source, true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(source).forEach(function (key) {
Object.defineProperty(
target,
key,
Object.getOwnPropertyDescriptor(source, key)
);
});
}
}
return target;
}
class MyHasScopeDirective extends graphqltools.SchemaDirectiveVisitor {
visitFieldDefinition(field) {
var expectedScopes = this.args.scopes;
var next = field.resolve; // wrap resolver with auth check
field.resolve = function (result, args, context, info) {
console.log(
"Field name " +
field.name +
" VFD - looking for scope " +
expectedScopes
);
var decoded = verifyAndDecodeToken({
context: context,
}); // FIXME: override with env var
var scopes =
decoded["Scopes"] ||
decoded["scopes"] ||
decoded["Scope"] ||
decoded["scope"] ||
[];
// if any requested scope matches
if (
expectedScopes.some(function (scope) {
return scopes.indexOf(scope) !== -1;
}) ||
expectedScopes.some(function (scope) {
return scopes.indexOf("[" + field.name + "]") !== -1;
})
) {
return next(
result,
args,
_objectSpread({}, context, {
user: decoded,
}),
info
);
}
throw new _errors.AuthorizationError({
message: "You are not authorized for this resource",
});
};
}
visitObject(obj) {
var fields = obj.getFields();
var expectedScopes = this.args.scopes;
Object.keys(fields).forEach(function (fieldName) {
var field = fields[fieldName];
var next = field.resolve;
field.resolve = function (result, args, context, info) {
console.log(
"Field name " +
field.name +
" VFD - looking for scope " +
expectedScopes
);
var decoded = verifyAndDecodeToken({
context: context,
}); // FIXME: override w/ env var
var scopes =
decoded["Scopes"] ||
decoded["scopes"] ||
decoded["Scope"] ||
decoded["scope"] ||
[];
if (
expectedScopes.some(function (role) {
return scopes.indexOf(role) !== -1;
})
) {
return next(
result,
args,
_objectSpread({}, context, {
user: decoded,
}),
info
);
}
throw new _errors.AuthorizationError({
message: "You are not authorized for this resource",
});
};
});
}
}
const schema = graphql.makeAugmentedSchema({
typeDefs,
mutations: true,
config: {
auth: {
isAuthenticated: true,
hasRole: true,
hasScope: true,
},
},
schemaDirectives: { hasScope: MyHasScopeDirective },
});
All the sessions of the conference are now available online