feat(router): custom authorization rules with the @policy directive - #1369
Open
rafagsiqueira wants to merge 1 commit into
Open
feat(router): custom authorization rules with the @policy directive#1369rafagsiqueira wants to merge 1 commit into
@policy directive#1369rafagsiqueira wants to merge 1 commit into
Conversation
Adds support for the federation `@policy` directive, evaluated by a coprocessor in the `graphql.analysis` stage. The router publishes the policies an operation depends on to the request context under `hive::authorization::required_policies` (read-only), the coprocessor answers with the subset it grants in `hive::authorization::granted_policies`, and authorization enforcement then runs against that decision. Policies left out of the answer are denied, so an absent or empty answer grants nothing. Unauthorized fields go through the existing `authorization.directives.unauthorized.mode` handling, and subgraph requests that would only resolve unauthorized fields are still never sent. `AuthorizationRule` becomes a struct instead of an enum so a field can carry `@authenticated`, `@requiresScopes` and `@policy` at once, all of which must be satisfied. `@policy` is independent of JWT authentication and stays enforced even when JWT is not configured. Closes graphql-hive#1134 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1134
Adds support for the federation
@policydirective, letting a coprocessor decide custom authorization rules the router cannot evaluate on its own.How it works
The router publishes what needs deciding, the coprocessor decides, the router enforces:
graphql.analysisstage,collect_required_policieswalks the operation (reusingOperationFilter, so@skip/@includeare honored) and writes every policy it depends on tohive::authorization::required_policies. Router-owned: a coprocessor writing to it fails the request.hive::authorization::granted_policies. Anything omitted is denied, so an absent or empty answer grants nothing.enforce_operation_authorizationnow runs after the analysis stage and feeds the granted policies into the existing filter/reject machinery.@policy(policies: [[...]])takes an OR of AND groups, the same shape as@requiresScopes. Access is granted when every policy of at least one group is granted.Unauthorized fields go through the existing
authorization.directives.unauthorized.modesetting:filter(default) nulls them and reportsUNAUTHORIZED_FIELD_OR_TYPE,rejectfails the operation. As with the other authorization directives, subgraph requests that would only resolve unauthorized fields are never sent — confirmed in the e2e logs.Example coprocessor answer for the
graphql.analysisstage:{ "version": 1, "control": "continue", "context": { "hive::authorization::granted_policies": ["read_profile"] } }Notable decisions
Granted-policies set rather than Apollo's
policy -> null|true|falsemap. It mirrors the existingprogressive_overridedomain (unresolved_labels/labels_to_override) and collapses to the same semantics — anything not granted is denied.AuthorizationRuleis now a struct instead of an enum. The previousAuthenticated | RequiresScopesenum could not express a field carrying both@requiresScopesand@policy. It now holdsauthenticated/scopes/policiesparts that must all be satisfied. This also fixed the union cross-product to combine scopes and policies independently.@policyis enforced without JWT. The old!jwt.enabled → return earlyshort-circuit would have disabled policies too. It is narrowed so that when JWT is off,@authenticated/@requiresScopesremain unenforced exactly as before (via the newenforce_jwt_rulesargument), while@policystill applies.Tests
policy_directive): OR-of-ANDs, type-level vs field-level policies,@policy+@authenticatedcombined, unknown policies ignored, and required-policy collection including@skipexclusion.e2e/supergraph-policy.graphql, driving a real mock coprocessor — including the reserved-key rejection and the no-policies-published case.cargo test_all1246 passed ·cargo test_e2e630 passed ·cargo test_plugin_examples28 passed ·cargo fmt --checkclean · no new clippy findings in changed files.Not included
Scoped to coprocessors as requested. The plugin-side write API (
RequestContextAuthorizationWrite) exists and is gated to theOnGraphqlAnalysishook for symmetry with the other context domains, but there is no plugin example or plugin-specific test for it.🤖 Generated with Claude Code