@requiresScopes
The @requiresScopes directive declares a GraphQL definition to require the agent (person, service, or device) to possess certain permissions. Lack of permissions will yield an authorization error.
Minimum requirements
Package | Minimum version |
---|---|
controlplane | 0.58.0 |
router | 0.60.0 |
wgc | 0.39.0 |
Make sure you have correctly set up Authentication & Authorization.
Definition
Arguments
Argument Name | Argument Type |
---|---|
scopes | [[openfed__Scope!]!]! |
The “scopes” argument requires an array (GraphQL List) of nested arrays.
The outmost array represents a set of OR scopes.
Each nested array sibling represents a set of AND scopes.
Each element in the AND scopes array should be an “openfed__Scope” scalar, which is an instance of permissions as defined in your authentication token. For example, “read:field”.
Declaration
OR Scopes
Consider the following @requiresScopes declared on Query.fieldOne:
If an agent wished to select Query.fieldOne, it would require EITHER the “read:field” permission OR the “read:scalar” permission.
AND Scopes
Consider the following @requiresScopes declared on Query.fieldTwo
If an agent wished to select Query.fieldTwo, it would require BOTH the “read:field” permission and the “read:scalar” permission. Lack of one or both would return an authorization error.
Combining OR and AND Scopes
Consider the following @requiresScopes declared on Query.fieldThree
If an agent wished to select Query.fieldThree, it would require at least one of the following sets of scopes: ((“read:field” AND “read:scalar”) OR (“read:query” AND “read:private”) OR (“read:all”))
Declaration
The @requiresScopes directive can be declared on enums, field definitions, interfaces, objects, and scalars. However, there are some differences between declaration on leaf definitions and parent definitions.
Declaration on leaf definitions (enums and scalars)
When @requiresScopes is declared on a leaf definition, the @requiresScopes directive (including its scopes) will be applied to all field definitions whose named type name (the innermost response type name) is the respective leaf definition within that subgraph.
If the same leaf definition is defined in another subgraph without @requiresScopes, the corresponding field definitions unique to that that subgraph will be unaffected. But note that @requiresScopes could be applied to those field definitions through other means.
If at least one instance of a shared field is declared with @requiresScopes, that field definition will be declared with @requiresScopes in the federated graph (see Federation). Consider the following example:
In subgraph-a, above, @requiresScopes has been declared on two leaf definitions:
- The enum “Enum”
- The scalar “Scalar”
And those leaf definitions are returned at the following field paths:
- Query.enumQuery (named type name is “Enum”)
- Query.scalarQuery (named type name is “Scalar”)
- Object.enumField (named type name is “Enum”)
- Object.scalarField (named type name is “Scalar”)
Consequently, @requiresScopes would be applied to all field definitions at the paths listed above. The normalized graph would look like so:
Declaration on object definitions
When @requiresScopes is declared on an object definition, the @requiresScopes directive (including its scopes) will be applied to all field definitions defined on the object definition within that subgraph.
If the same object definition is defined in another subgraph without @requiresScopes, the corresponding field definitions unique to that that subgraph will be unaffected. But note that @requiresScopes could be applied to those field definitions through other means.
If at least one instance of a shared field is declared with @requiresScopes, that field definition will be declared with @requiresScopes in the federated graph (see Federation). Consider the following example:
In subgraph-b, above, @requiresScopes has been declared on two object definitions:
- The root object “Query”
- The object “Object”
And those object definitions define the following field definitions:
- Query.objectQuery
- Query.objectsQuery
- Object.intField
- Object.stringField
Consequently, the @requiresScopes directive (including its defined scopes) would be applied to all field definitions at the paths listed above. The normalized graph would look like so:
Declaration on interface definitions
When @requiresScopes is declared on an interface definition, the @requiresScopes directive (including its scopes) will be applied to all field definitions defined on the interface definition within that subgraph.
If the same interface definition is defined in another subgraph without @authenticated, the corresponding field definitions unique to that subgraph be unaffected. But note that @requiresScopes could be applied to those field definitions through other means.
In addition, @requiresScopes will be applied to the corresponding field definitions defined on the objects that implement that interface within that subgraph.
If at least one instance of a shared field is declared with @requiresScopes, that field definition will be declared with @requiresScopes in the federated graph (see Federation). Consider the following example:
In subgraph-c, above, @requiresScopes has been declared on the interface definition “Interface”, which is implemented by two object definitions:
- Object
- AnotherObject
This interface defines the following field definitions:
- Interface.intField
- Interface.stringField
Consequently, the @requiresScopes directive (including its defined scopes) would be applied to all field definitions at the paths listed above, in addition to the same field definitions that are defined on the object definitions that implement that interface.
The normalized graph would look like so:
When @requiresScopes is declared on an interface field definition directly, the corresponding field definitions on the object types that implement that interface within that subgraph will also declare @requiresScopes (including its defined scopes). For example:
The subgraph above, subgraph-d, would normalize into the following subgraph:
Combining multiple scopes (matrix multiplication)
Sometimes, a field definition must combine multiple declarations of @requiresScopes.
Consider the following example:
In this instance, the field definition Query.enumField is “inheriting” two other sets of scopes (“read:query” and “read:enum”) in addition to its own set of scopes (“read:private”).
These scopes are combined through the “AND” operator:
But sometimes, a field definition must combine more complicated declarations of @requiresScopes. These such cases are combined through matrix multiplication to ensure all contributed scopes are respected simultaneously.
Consider the following example:
In this instance, the field definition Query.enumField is once again “inheriting” two more declarations of @requiresScopes. But this time, there are multiple sets of OR and AND scopes, and each set of OR scopes is required by each other set of OR scopes.
Consequently, the “read:query” AND scope declared on Query must be added to both sets of OR scopes declared on Query.enumField. And the same again should happen with the “read:root” AND scope.
Finally, the scopes defined on Enum must also be applied. In this case, it’s just a single AND scope (“read:enum”), so it can be applied without creating any new sets. In the event that Enum defined several OR scopes, a new set of scopes would be created for each.
The results are four new sets representing AND scopes, and each set of AND scopes represents an entire OR scope:
Which would appear in the normalized subgraph like so:
Federation
The @requiresScopes directive will persist in the federated schema. Consequently, if @requiresScopes is declared on a field definition in one graph, and the same field definition (a shared field) is defined in another graph without @requiresScopes, then @requiresScopes will still be declared on the federated field. This also means selecting this field will always require the relevant permissions, regardless of whether it would be resolved from a subgraph that did not declare @requiresScopes. Consider the following two subgraphs and the resulting federated graph. The federated graph includes descriptions explaining how each @requiresScopes directive has persisted.
Errors
In the event that an agent without relevant permissions selects a non-nullable field that is declared with @requiresScopes, an authorization error will be returned, and the entire data will be null (see Non-nullable authenticated data requested among unauthenticated data).
In the event that an agent without relevant permissions selects a nullable field that is declared with @requiresScopes, an authorization error will be returned, and the specific field will be null (see Partial data):
Partial data (nullable data requiring permissions)
Imagine an agent without relevant permissions selects a field that is declared with @requiresScopes and the response type of that field is nullable. However, the agent also selects a field that is not declared @requiresScopes (nor are any potential nested fields). In this event, an authorization error will still be returned, but the specific data that requires authentication will be null, while the data not requiring authentication will be returned. Consider the following federated graph and corresponding query:
An agent without any permissions sending the query above would receive something like the following:
Non-nullable authenticated data requested among unauthenticated data
In the event an agent without relevant permissions selects any non-nullable fields that declare @requiresScopes and therefore require one or more permissions, an authorization error will be returned, and the entire data will return null. This is true even if one or more field selections did not require permissions or are nullable. Consider the following federated graph and corresponding query:
An agent without the “read:int” permission sending the query above would receive something like the following:
Partial permissions
An agent must have all relevant permissions within at least one entire set of AND scopes among the OR scopes declared through @requiresScopes for a selected field to return data. In the event that the agent has some but not all permissions, the error message will be transparent: