⚙️ Mandatory directives for fields/directives in GraphQL by PoP
I just added support for a lovely new feature in GraphQL by PoP: the ability to execute mandatory directives whenever a specific field or directive is present in the GraphQL query. This means that the GraphQL engine will:
- Whenever a specific field from some type is present in the query, add a certain directive (or directives) to be executed on the field
- Whenever a specific directive is invoked, execute another directive (or directives) before it
A mandatory directive can, itself, have its own set of mandatory directives which are also added to the directive chain up.
This feature is extremely powerful, since it allows to easily configure what directives are added to the query under what circumstances, as to implement any IFTTT strategy. It supports adding the following capabilities to our GraphQL API:
Define the cache control max-age a field by field basis
Attach a @CacheControl
directive to all fields, customizing the value of the maxAge
parameter: 1 year for the Post
's field url
, and 1 hour for field title
.
Set-up access control
Attach a @validateDoesLoggedInUserHaveAnyRole
directive to field email
from the User
type, so only the admins can query the user email.
Synchronize access-control with cache-control
By chaining up directives, we can make sure that, whenever validating if the user can access a field/directive, the response will not be cached. For instance:
- Attach directive
@validateIsUserLoggedIn
to fieldme
- Attach directive
@CacheControl
withmaxAge
argument value of0
to directive@validateIsUserLoggedIn
.
Beef up security
Attach a @validateIsUserLoggedIn
directive to directive @translate
, to avoid malicious actors executing queries against the GraphQL service that can bring the server down and spike its bills (in this case, @translate
is based on Google Translate and it pays a fee to use this service)
Demostration permalink
In this schema, the User
type has fields roles
and capabilities
, which I consider to be sensitive information, so it should not be accessible by the random user.
Then, I created package Access Control List for User Roles to attach directive @validateDoesLoggedInUserHaveAnyRole
to these two fields, configured to validate that only a user with a given role can access them (code here):
if ($roles = Environment::anyRoleLoggedInUserMustHaveToAccessRolesFields()) {
ContainerBuilderUtils::injectValuesIntoService(
'access_control_manager',
'addEntriesForFields',
UserRolesAccessControlGroups::ROLES,
[
[RootTypeResolver::class, 'roles', $roles],
[RootTypeResolver::class, 'capabilities', $roles],
[UserTypeResolver::class, 'roles', $roles],
[UserTypeResolver::class, 'capabilities', $roles],
]
);
}
When executing the query, dear reader, you won't be allowed to access those fields, since you are not logged in (which is validated before checking if the user has the required role):