Skip to content

composition updates related to merging auth directives#3321

Merged
dariuszkuc merged 5 commits intonextfrom
fed_2.12_auth
Nov 4, 2025
Merged

composition updates related to merging auth directives#3321
dariuszkuc merged 5 commits intonextfrom
fed_2.12_auth

Conversation

@dariuszkuc
Copy link
Member

Composition updates:

  • reject authorization directives on interface fields
  • enforce transitive authorization requirements on @requires and @fromContext
  • stricter merge rules for @requiresScopes and @policy (change from OR to AND logic)
  • automatically propagate authorization requirements from types to interfaces in the supergraph

Restricts usage of `@authenticated`, `@policy` and `@requiresScopes` from being applied on interfaces, interface objects and their fields.

GraphQL spec currently does not define any interface inheritance rules and developers have to explicitly redefine all interface fields on their implementations. At runtime, GraphQL servers cannot return abstract types and always return concrete output types. Due to the above, applying auth directives on the interfaces may lead to unexpected runtime behavior as they won't have any effect at runtime.
Adds new `postMergeValidation` check to ensure that all fields that depends on data from other parts of the supergraph through `@requires` and/or `@fromContext` directives explicitly specify matching `@authenticated`, `@requiresScopes` and/or `@policy` auth requirements, e.g.

```graphql
type T @key(fields: "id") {
  id: ID!
  extra: String @external
  # we need explicit @authenticated as it is needed to access extra
  requiresExtra: String @requires(fields: "extra") @authenticated
}

type T @key(fields: "id") {
  id: ID!
  extra: String @authenticated
}
```
Current merge policies for `@authenticated`, `@requiresScopes` and `@policy` were inconsistent.

If single subgraph declared a field with one of the directives then it would restrict access to this supergraph field regardless which subgraph would resolve this field (results in `AND` rule for any applied auth directive, i.e. `@authenticated` AND `@policy` is required to access this field). If the same auth directive (`@requiresScopes`/`@policy`) were applied across the subgraphs then the resulting supergraph field could be resolved by fullfilling either one of the subgraph requirements (resulting in `OR` rule, i.e. either `@policy` 1 or `@policy` 2 has to be true to access the field). While arguably this allowed for easier schema evolution, it did result in weakening the security requirements.

Since `@policy` and `@requiresScopes` values are represent boolean conditions in Disjunctive Normal Form, we can merge them conjunctively to get the final auth requirements, i.e.

```graphql
type T @authenticated {
  # requires scopes (A1 AND A2) OR A3
  secret: String @requiresScopes(scopes: [["A1", "A2"], ["A3"]])
}

type T {
  # requires scopes B1 OR B2
  secret: String @requiresScopes(scopes: [["B1"], ["B2"]]
}

type T @authenticated {
  secret: String @requiresScopes(
    scopes: [
      ["A1", "A2", "B1"],
      ["A1", "A2", "B2"],
      ["A3", "B1"],
      ["A3", "B2"]
    ])
}
```

This algorithm also deduplicates redundant requirements, e.g.

```graphql
type T {
  # requires A1 AND A2 scopes to access
  secret: String @requiresScopes(scopes: [["A1", "A2"]])
}

type T {
  # requires only A1 scope to access
  secret: String @requiresScopes(scopes: [["A1"]])
}

type T {
  # requires only A1 scope to access as A2 is redundant
  secret: String @requiresScopes(scopes: [["A1"]])
}
```
… in supergraph

Propagate auth requirements from polymorphic types upwards to their interfaces. This ensures consistent router runtime behavior regardless of the router version.
@dariuszkuc dariuszkuc requested a review from a team as a code owner November 4, 2025 15:19
@changeset-bot
Copy link

changeset-bot bot commented Nov 4, 2025

🦋 Changeset detected

Latest commit: 1fe8281

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
@apollo/composition Patch
@apollo/federation-internals Patch
@apollo/gateway Patch
@apollo/query-planner Patch
@apollo/query-graphs Patch
@apollo/subgraph Patch
apollo-federation-integration-testsuite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@apollo-librarian
Copy link

apollo-librarian bot commented Nov 4, 2025

✅ Docs preview has no changes

The preview was not built because there were no changes.

Build ID: 87ecff8ace6df3e8cf967e6f
Build Logs: View logs

@codesandbox-ci
Copy link

codesandbox-ci bot commented Nov 4, 2025

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@dariuszkuc dariuszkuc merged commit 131e572 into next Nov 4, 2025
18 checks passed
@dariuszkuc dariuszkuc deleted the fed_2.12_auth branch November 4, 2025 15:35
@github-actions github-actions bot mentioned this pull request Nov 4, 2025
dariuszkuc added a commit that referenced this pull request Nov 4, 2025
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to next, this PR will
be updated.


# Releases
## @apollo/composition@2.12.0

### Minor Changes

- Federation 2.12 and Connect 0.3
([#3276](#3276))

- Add connect spec v0.2
([#3228](#3228))

- Federation v2.12 release
([#3323](#3323))

- Added isSuccess argument to @connect and @source
([#3294](#3294))

- Fixes a bug where composition may not generate a satisfiability error
for an unsatisfiable `@shareable` mutation field.
([#3305](#3305))
([#3305](#3305))

### Patch Changes

- Preparing preview.2 release
([#3255](#3255))

- Automatically propagate authorization requirements from implementing
type to interface in the supergraph.
([#3321](#3321))

Authorization requirements now automatically propagate from implementing
types to interfaces during composition. Direct auth specifications on
interfaces are no longer allowed. Interface access requires satisfying
ALL implementing types' requirements (`AND` rule), with these
requirements included in the supergraph for backward compatibility with
older routers.

- Fix transitive auth requirements on `@requires` and `@fromcontext`
([#3321](#3321))

Adds new `postMergeValidation` check to ensure that all fields that
depends on data from other parts of the supergraph through `@requires`
and/or `@fromContext` directives explicitly specify matching
`@authenticated`, `@requiresScopes` and/or `@policy` auth requirements,
e.g.

    ```graphql
    type T @key(fields: "id") {
      id: ID!
      extra: String @external
# we need explicit `@authenticated` as it is needed to access extra
      requiresExtra: String @requires(fields: "extra") @authenticated
    }

    type T @key(fields: "id") {
      id: ID!
      extra: String @authenticated
    }
    ```

- Preparing new preview release 2.12.0-preview.3 (patch).
([#3308](#3308))

- Adding new CompositionOption `maxValidationSubgraphPaths`. This value
represents the maximum number of SubgraphPathInfo objects that may exist
in a ValidationTraversal when checking for satisfiability. Setting this
value can help composition error before running out of memory. Default
is 1,000,000.
([#3275](#3275))

- Restrict usage of auth directives on interfaces
([#3321](#3321))

Restricts usage of `@authenticated`, `@policy` and `@requiresScopes`
from being applied on interfaces, interface objects and their fields.

GraphQL spec currently does not define any interface inheritance rules
and developers have to explicitly redefine all interface fields on their
implementations. At runtime, GraphQL servers cannot return abstract
types and always return concrete output types. Due to the above,
applying auth directives on the interfaces may lead to unexpected
runtime behavior as they won't have any effect at runtime.

- Allow merging external types when using arrays as default arguments.
([#3096](#3096))

- Stricter merge rules for @requiresScopes and @Policy
([#3321](#3321))

Current merge policies for `@authenticated`, `@requiresScopes` and
`@policy` were inconsistent.

If a shared field uses the same authorization directives across
subgraphs, composition merges them using `OR` logic. However, if a
shared field uses different authorization directives across subgraphs
composition merges them using `AND` logic. This simplified schema
evolution, but weakened security requirements. Therefore, the behavior
has been changed to always apply `AND` logic to authorization directives
applied to the same field across subgraphs.

Since `@policy` and `@requiresScopes` values represent boolean
conditions in Disjunctive Normal Form, we can merge them conjunctively
to get the final auth requirements. For example:

    ```graphql
    # subgraph A
    type T @authenticated {
      # requires scopes (A1 AND A2) OR A3
      secret: String @requiresScopes(scopes: [["A1", "A2"], ["A3"]])
    }

    # subgraph B
    type T {
      # requires scopes B1 OR B2
      secret: String @requiresScopes(scopes: [["B1"], ["B2"]]
    }

    # composed supergraph
    type T @authenticated {
      secret: String @requiresScopes(
        scopes: [
          ["A1", "A2", "B1"],
          ["A1", "A2", "B2"],
          ["A3", "B1"],
          ["A3", "B2"]
        ])
    }
    ```

    This algorithm also deduplicates redundant requirements, e.g.

    ```graphql
    # subgraph A
    type T {
      # requires A1 AND A2 scopes to access
      secret: String @requiresScopes(scopes: [["A1", "A2"]])
    }

    # subgraph B
    type T {
      # requires only A1 scope to access
      secret: String @requiresScopes(scopes: [["A1"]])
    }

    # composed supergraph
    type T {
      # requires only A1 scope to access as A2 is redundant
      secret: String @requiresScopes(scopes: [["A1"]])
    }
    ```

- Fixed handling `@requires` dependency on fields returned by
`@interfaceObject`
([#3318](#3318))

Depending on the merge order of the types, we could fail composition if
a type that `@requires` data from an `@interfaceObject` is merged before
the interface. Updated merge logic to use explicit merge order of
scalars, input objects, interfaces, and finally objects.

- Updated dependencies
\[[`3e2b0a8569a9fe46726182887ed0b4bfc0b52468`](3e2b0a8),
[`bb4614d338ae03bac51a5fc2439590f172c4e54d`](bb4614d),
[`99f2da21de88f9ad9a32ee7ed64b2d4a92887b40`](99f2da2),
[`468f27842608f4e390cfc88bc7e6b4b0945f95ff`](468f278),
[`3fd5157b309f1d3439b2d87c67b0601fb246d04c`](3fd5157),
[`b734ea04d118db09cf6077fdd968c8f04a96327a`](b734ea0),
[`4bda3a498eba36e187dfd9ae673eca12d3f3502c`](4bda3a4),
[`e7e67579908d5cd2fa6fe558228dffe4808cd98d`](e7e6757),
[`f3ab499eaf62b1a1c0f08b838d2cbde5accb303a`](f3ab499),
[`faea2d1174d80593264f2227cfde9a2ba1a59b96`](faea2d1),
[`97b9d2edfcfeed99124f9e115f992cbef3804682`](97b9d2e),
[`f6af504f1ba8283fd00af0d6e3c9c1a665d62736`](f6af504),
[`a595235d3cf8f67611efd8395332b64d067b5f1f`](a595235)]:
    -   @apollo/query-graphs@2.12.0
    -   @apollo/federation-internals@2.12.0

## @apollo/gateway@2.12.0

### Minor Changes

- Federation 2.12 and Connect 0.3
([#3276](#3276))

- Add connect spec v0.2
([#3228](#3228))

- Federation v2.12 release
([#3323](#3323))

### Patch Changes

- Preparing preview.2 release
([#3255](#3255))

- Updated dependencies
\[[`3e2b0a8569a9fe46726182887ed0b4bfc0b52468`](3e2b0a8),
[`bb4614d338ae03bac51a5fc2439590f172c4e54d`](bb4614d),
[`99f2da21de88f9ad9a32ee7ed64b2d4a92887b40`](99f2da2),
[`468f27842608f4e390cfc88bc7e6b4b0945f95ff`](468f278),
[`3fd5157b309f1d3439b2d87c67b0601fb246d04c`](3fd5157),
[`b734ea04d118db09cf6077fdd968c8f04a96327a`](b734ea0),
[`4bda3a498eba36e187dfd9ae673eca12d3f3502c`](4bda3a4),
[`e7e67579908d5cd2fa6fe558228dffe4808cd98d`](e7e6757),
[`f3ab499eaf62b1a1c0f08b838d2cbde5accb303a`](f3ab499),
[`faea2d1174d80593264f2227cfde9a2ba1a59b96`](faea2d1),
[`0dbc7cc72ffacf324231e9ccb2de4189f6bf3289`](0dbc7cc),
[`97b9d2edfcfeed99124f9e115f992cbef3804682`](97b9d2e),
[`f6af504f1ba8283fd00af0d6e3c9c1a665d62736`](f6af504),
[`bc07e979b9fd24c9b94740b170f11023fe99ba1e`](bc07e97),
[`a595235d3cf8f67611efd8395332b64d067b5f1f`](a595235),
[`9cbdcb53f859c877a476e2725faa4cb205506f57`](9cbdcb5)]:
    -   @apollo/query-planner@2.12.0
    -   @apollo/composition@2.12.0
    -   @apollo/federation-internals@2.12.0

## @apollo/federation-internals@2.12.0

### Minor Changes

- Federation 2.12 and Connect 0.3
([#3276](#3276))

- Add connect spec v0.2
([#3228](#3228))

- Federation v2.12 release
([#3323](#3323))

- Added isSuccess argument to @connect and @source
([#3294](#3294))

### Patch Changes

- Preparing preview.2 release
([#3255](#3255))

- Automatically propagate authorization requirements from implementing
type to interface in the supergraph.
([#3321](#3321))

Authorization requirements now automatically propagate from implementing
types to interfaces during composition. Direct auth specifications on
interfaces are no longer allowed. Interface access requires satisfying
ALL implementing types' requirements (`AND` rule), with these
requirements included in the supergraph for backward compatibility with
older routers.

- Fix transitive auth requirements on `@requires` and `@fromcontext`
([#3321](#3321))

Adds new `postMergeValidation` check to ensure that all fields that
depends on data from other parts of the supergraph through `@requires`
and/or `@fromContext` directives explicitly specify matching
`@authenticated`, `@requiresScopes` and/or `@policy` auth requirements,
e.g.

    ```graphql
    type T @key(fields: "id") {
      id: ID!
      extra: String @external
# we need explicit `@authenticated` as it is needed to access extra
      requiresExtra: String @requires(fields: "extra") @authenticated
    }

    type T @key(fields: "id") {
      id: ID!
      extra: String @authenticated
    }
    ```

- Preparing new preview release 2.12.0-preview.3 (patch).
([#3308](#3308))

- Adding new CompositionOption `maxValidationSubgraphPaths`. This value
represents the maximum number of SubgraphPathInfo objects that may exist
in a ValidationTraversal when checking for satisfiability. Setting this
value can help composition error before running out of memory. Default
is 1,000,000.
([#3275](#3275))

- Fixed demand control validations
([#3314](#3314))

Updated `@cost`/`@listSize` validations to use correct federation spec
to look them up in the schema.

- Restrict usage of auth directives on interfaces
([#3321](#3321))

Restricts usage of `@authenticated`, `@policy` and `@requiresScopes`
from being applied on interfaces, interface objects and their fields.

GraphQL spec currently does not define any interface inheritance rules
and developers have to explicitly redefine all interface fields on their
implementations. At runtime, GraphQL servers cannot return abstract
types and always return concrete output types. Due to the above,
applying auth directives on the interfaces may lead to unexpected
runtime behavior as they won't have any effect at runtime.

- Stricter merge rules for @requiresScopes and @Policy
([#3321](#3321))

Current merge policies for `@authenticated`, `@requiresScopes` and
`@policy` were inconsistent.

If a shared field uses the same authorization directives across
subgraphs, composition merges them using `OR` logic. However, if a
shared field uses different authorization directives across subgraphs
composition merges them using `AND` logic. This simplified schema
evolution, but weakened security requirements. Therefore, the behavior
has been changed to always apply `AND` logic to authorization directives
applied to the same field across subgraphs.

Since `@policy` and `@requiresScopes` values represent boolean
conditions in Disjunctive Normal Form, we can merge them conjunctively
to get the final auth requirements. For example:

    ```graphql
    # subgraph A
    type T @authenticated {
      # requires scopes (A1 AND A2) OR A3
      secret: String @requiresScopes(scopes: [["A1", "A2"], ["A3"]])
    }

    # subgraph B
    type T {
      # requires scopes B1 OR B2
      secret: String @requiresScopes(scopes: [["B1"], ["B2"]]
    }

    # composed supergraph
    type T @authenticated {
      secret: String @requiresScopes(
        scopes: [
          ["A1", "A2", "B1"],
          ["A1", "A2", "B2"],
          ["A3", "B1"],
          ["A3", "B2"]
        ])
    }
    ```

    This algorithm also deduplicates redundant requirements, e.g.

    ```graphql
    # subgraph A
    type T {
      # requires A1 AND A2 scopes to access
      secret: String @requiresScopes(scopes: [["A1", "A2"]])
    }

    # subgraph B
    type T {
      # requires only A1 scope to access
      secret: String @requiresScopes(scopes: [["A1"]])
    }

    # composed supergraph
    type T {
      # requires only A1 scope to access as A2 is redundant
      secret: String @requiresScopes(scopes: [["A1"]])
    }
    ```

## @apollo/query-graphs@2.12.0

### Minor Changes

- Federation 2.12 and Connect 0.3
([#3276](#3276))

- Add connect spec v0.2
([#3228](#3228))

- Federation v2.12 release
([#3323](#3323))

### Patch Changes

- Preparing preview.2 release
([#3255](#3255))

- Fixes a bug where query planning may unexpectedly error due to
attempting to generate a plan where a `@shareable` mutation field is
called more than once across multiple subgraphs.
([#3304](#3304))
([#3304](#3304))

- Updated dependencies
\[[`3e2b0a8569a9fe46726182887ed0b4bfc0b52468`](3e2b0a8),
[`bb4614d338ae03bac51a5fc2439590f172c4e54d`](bb4614d),
[`99f2da21de88f9ad9a32ee7ed64b2d4a92887b40`](99f2da2),
[`468f27842608f4e390cfc88bc7e6b4b0945f95ff`](468f278),
[`3fd5157b309f1d3439b2d87c67b0601fb246d04c`](3fd5157),
[`b734ea04d118db09cf6077fdd968c8f04a96327a`](b734ea0),
[`4bda3a498eba36e187dfd9ae673eca12d3f3502c`](4bda3a4),
[`e7e67579908d5cd2fa6fe558228dffe4808cd98d`](e7e6757),
[`faea2d1174d80593264f2227cfde9a2ba1a59b96`](faea2d1),
[`97b9d2edfcfeed99124f9e115f992cbef3804682`](97b9d2e),
[`f6af504f1ba8283fd00af0d6e3c9c1a665d62736`](f6af504),
[`a595235d3cf8f67611efd8395332b64d067b5f1f`](a595235)]:
    -   @apollo/federation-internals@2.12.0

## @apollo/query-planner@2.12.0

### Minor Changes

- Federation 2.12 and Connect 0.3
([#3276](#3276))

- Add connect spec v0.2
([#3228](#3228))

- Federation v2.12 release
([#3323](#3323))

### Patch Changes

- Preparing preview.2 release
([#3255](#3255))

- Fixes a bug where query planning may unexpectedly error due to
attempting to generate a plan where a `@shareable` mutation field is
called more than once across multiple subgraphs.
([#3304](#3304))
([#3304](#3304))

- Updated dependencies
\[[`3e2b0a8569a9fe46726182887ed0b4bfc0b52468`](3e2b0a8),
[`bb4614d338ae03bac51a5fc2439590f172c4e54d`](bb4614d),
[`99f2da21de88f9ad9a32ee7ed64b2d4a92887b40`](99f2da2),
[`468f27842608f4e390cfc88bc7e6b4b0945f95ff`](468f278),
[`3fd5157b309f1d3439b2d87c67b0601fb246d04c`](3fd5157),
[`b734ea04d118db09cf6077fdd968c8f04a96327a`](b734ea0),
[`4bda3a498eba36e187dfd9ae673eca12d3f3502c`](4bda3a4),
[`e7e67579908d5cd2fa6fe558228dffe4808cd98d`](e7e6757),
[`f3ab499eaf62b1a1c0f08b838d2cbde5accb303a`](f3ab499),
[`faea2d1174d80593264f2227cfde9a2ba1a59b96`](faea2d1),
[`97b9d2edfcfeed99124f9e115f992cbef3804682`](97b9d2e),
[`f6af504f1ba8283fd00af0d6e3c9c1a665d62736`](f6af504),
[`a595235d3cf8f67611efd8395332b64d067b5f1f`](a595235)]:
    -   @apollo/query-graphs@2.12.0
    -   @apollo/federation-internals@2.12.0

## @apollo/subgraph@2.12.0

### Minor Changes

- Federation 2.12 and Connect 0.3
([#3276](#3276))

- Add connect spec v0.2
([#3228](#3228))

- Federation v2.12 release
([#3323](#3323))

### Patch Changes

- Preparing preview.2 release
([#3255](#3255))

- When a `GraphQLScalarType` resolver is provided to
`buildSubgraphSchema()`, omitted configuration options in the
`GraphQLScalarType` no longer cause the corresponding properties in the
GraphQL document/AST to be cleared. To explicitly clear these
properties, use `null` for the configuration option instead.
([#3287](#3287))

- Updated dependencies
\[[`3e2b0a8569a9fe46726182887ed0b4bfc0b52468`](3e2b0a8),
[`bb4614d338ae03bac51a5fc2439590f172c4e54d`](bb4614d),
[`99f2da21de88f9ad9a32ee7ed64b2d4a92887b40`](99f2da2),
[`468f27842608f4e390cfc88bc7e6b4b0945f95ff`](468f278),
[`3fd5157b309f1d3439b2d87c67b0601fb246d04c`](3fd5157),
[`b734ea04d118db09cf6077fdd968c8f04a96327a`](b734ea0),
[`4bda3a498eba36e187dfd9ae673eca12d3f3502c`](4bda3a4),
[`e7e67579908d5cd2fa6fe558228dffe4808cd98d`](e7e6757),
[`faea2d1174d80593264f2227cfde9a2ba1a59b96`](faea2d1),
[`97b9d2edfcfeed99124f9e115f992cbef3804682`](97b9d2e),
[`f6af504f1ba8283fd00af0d6e3c9c1a665d62736`](f6af504),
[`a595235d3cf8f67611efd8395332b64d067b5f1f`](a595235)]:
    -   @apollo/federation-internals@2.12.0

## apollo-federation-integration-testsuite@2.12.0

### Minor Changes

- Federation 2.12 and Connect 0.3
([#3276](#3276))

- Add connect spec v0.2
([#3228](#3228))

- Federation v2.12 release
([#3323](#3323))

### Patch Changes

- Preparing preview.2 release
([#3255](#3255))

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dariuszkuc <9501705+dariuszkuc@users.noreply.github.com>
dariuszkuc added a commit that referenced this pull request Nov 4, 2025
)

Composition updates:
* reject authorization directives on interface fields
* enforce transitive authorization requirements on `@requires` and
`@fromContext`
* stricter merge rules for `@requiresScopes` and `@policy` (change from
`OR` to `AND` logic)
* automatically propagate authorization requirements from types to
interfaces in the supergraph

Backport of #3321
dariuszkuc added a commit that referenced this pull request Nov 4, 2025
)

Composition updates:
* reject authorization directives on interface fields
* enforce transitive authorization requirements on `@requires` and
`@fromContext`
* stricter merge rules for `@requiresScopes` and `@policy` (change from
`OR` to `AND` logic)
* automatically propagate authorization requirements from types to
interfaces in the supergraph

Backport of #3321
dariuszkuc added a commit that referenced this pull request Nov 4, 2025
…#3327)

Composition updates:
* reject authorization directives on interface fields
* enforce transitive authorization requirements on `@requires` and
`@fromContext`
* stricter merge rules for `@requiresScopes` and `@policy` (change from
`OR` to `AND` logic)
* automatically propagate authorization requirements from types to
interfaces in the supergraph

Backport of #3321
const filtered: T[][][] = [];
const seen = new Set<string>;
values.forEach((value) => {
value.sort();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an array of arrays, .sort() won't do what you expect. Specifically, it stringifies the values (and not JSON), so e.g. ["a", "b"] will stringify to the same as ["a,b"], i.e. a,b. You'll want to use the compareFn argument to .sort() here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed this does not sort inner most array values so given [["c"], ["b", "a"]] we end up with [["b", "a"], ["c"]] (instead of expected [["a", "b"], ["c"]). I'll add the extra sort

const policyDirective = metadata.policyDirective();
[authenticatedDirective, requiresScopesDirective, policyDirective].forEach((directive) => {
for (const application of directive.applications()) {
const element = application.parent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that element has any type here, which may cause type errors downstream.

It's better to use, e.g.:

const element: SchemaElement<any, any> = application.parent;

and then with that, it becomes more clear what changes need to happen downstream. E.g. isAppliedOnInterface() should accept SchemaElement<any, any> instead of Type.

You may need to add a function that looks like:

export function isElementNamedType(elem: SchemaElement<any, any>): elem is NamedType {
  return elem instanceof BaseNamedType;
}

to the end of internals-js/src/definitions.ts.

dariuszkuc added a commit to apollographql/router that referenced this pull request Jan 20, 2026
Current merge policies for `@authenticated`, `@requiresScopes` and `@policy` were inconsistent.

If single subgraph declared a field with one of the directives then it would restrict access to this supergraph field regardless which subgraph would resolve this field (results in AND rule for any applied auth directive, i.e. `@authenticated` AND `@policy` is required to access this field). If the same auth directive (`@requiresScopes`/`@policy`) were applied across the subgraphs then the resulting supergraph field could be resolved by fullfilling either one of the subgraph requirements (resulting in OR rule, i.e. either `@policy` 1 or `@policy` 2 has to be true to access the field). While arguably this allowed for easier schema evolution, it did result in weakening the security requirements.

Since `@policy` and `@requiresScopes` values are represent boolean conditions in Disjunctive Normal Form, we can merge them conjunctively to get the final auth requirements, i.e.

```graphql
type T @authenticated {
  # requires scopes (A1 AND A2) OR A3
  secret: String @requiresScopes(scopes: [["A1", "A2"], ["A3"]])
}

type T {
  # requires scopes B1 OR B2
  secret: String @requiresScopes(scopes: [["B1"], ["B2"]]
}

type T @authenticated {
  secret: String @requiresScopes(
    scopes: [
      ["A1", "A2", "B1"],
      ["A1", "A2", "B2"],
      ["A3", "B1"],
      ["A3", "B2"]
    ])
}
```

This algorithm also deduplicates redundant requirements, e.g.

```graphql
type T {
  # requires A1 AND A2 scopes to access
  secret: String @requiresScopes(scopes: [["A1", "A2"]])
}

type T {
  # requires only A1 scope to access
  secret: String @requiresScopes(scopes: [["A1"]])
}

type T {
  # requires only A1 scope to access as A2 is redundant
  secret: String @requiresScopes(scopes: [["A1"]])
}
```

<!-- FED-853 -->

Partial backport of apollographql/federation#3321 and apollographql/federation#3343
dariuszkuc added a commit to apollographql/router that referenced this pull request Jan 21, 2026
Current merge policies for `@authenticated`, `@requiresScopes` and `@policy` were inconsistent.

If single subgraph declared a field with one of the directives then it would restrict access to this supergraph field regardless which subgraph would resolve this field (results in AND rule for any applied auth directive, i.e. `@authenticated` AND `@policy` is required to access this field). If the same auth directive (`@requiresScopes`/`@policy`) were applied across the subgraphs then the resulting supergraph field could be resolved by fullfilling either one of the subgraph requirements (resulting in OR rule, i.e. either `@policy` 1 or `@policy` 2 has to be true to access the field). While arguably this allowed for easier schema evolution, it did result in weakening the security requirements.

Since `@policy` and `@requiresScopes` values are represent boolean conditions in Disjunctive Normal Form, we can merge them conjunctively to get the final auth requirements, i.e.

```graphql
type T @authenticated {
  # requires scopes (A1 AND A2) OR A3
  secret: String @requiresScopes(scopes: [["A1", "A2"], ["A3"]])
}

type T {
  # requires scopes B1 OR B2
  secret: String @requiresScopes(scopes: [["B1"], ["B2"]]
}

type T @authenticated {
  secret: String @requiresScopes(
    scopes: [
      ["A1", "A2", "B1"],
      ["A1", "A2", "B2"],
      ["A3", "B1"],
      ["A3", "B2"]
    ])
}
```

This algorithm also deduplicates redundant requirements, e.g.

```graphql
type T {
  # requires A1 AND A2 scopes to access
  secret: String @requiresScopes(scopes: [["A1", "A2"]])
}

type T {
  # requires only A1 scope to access
  secret: String @requiresScopes(scopes: [["A1"]])
}

type T {
  # requires only A1 scope to access as A2 is redundant
  secret: String @requiresScopes(scopes: [["A1"]])
}
```

<!-- FED-853 -->

Partial backport of apollographql/federation#3321 and apollographql/federation#3343
dariuszkuc added a commit to apollographql/router that referenced this pull request Jan 21, 2026
Current merge policies for `@authenticated`, `@requiresScopes` and `@policy` were inconsistent.

If single subgraph declared a field with one of the directives then it would restrict access to this supergraph field regardless which subgraph would resolve this field (results in AND rule for any applied auth directive, i.e. `@authenticated` AND `@policy` is required to access this field). If the same auth directive (`@requiresScopes`/`@policy`) were applied across the subgraphs then the resulting supergraph field could be resolved by fullfilling either one of the subgraph requirements (resulting in OR rule, i.e. either `@policy` 1 or `@policy` 2 has to be true to access the field). While arguably this allowed for easier schema evolution, it did result in weakening the security requirements.

Since `@policy` and `@requiresScopes` values are represent boolean conditions in Disjunctive Normal Form, we can merge them conjunctively to get the final auth requirements, i.e.

```graphql
type T @authenticated {
  # requires scopes (A1 AND A2) OR A3
  secret: String @requiresScopes(scopes: [["A1", "A2"], ["A3"]])
}

type T {
  # requires scopes B1 OR B2
  secret: String @requiresScopes(scopes: [["B1"], ["B2"]]
}

type T @authenticated {
  secret: String @requiresScopes(
    scopes: [
      ["A1", "A2", "B1"],
      ["A1", "A2", "B2"],
      ["A3", "B1"],
      ["A3", "B2"]
    ])
}
```

This algorithm also deduplicates redundant requirements, e.g.

```graphql
type T {
  # requires A1 AND A2 scopes to access
  secret: String @requiresScopes(scopes: [["A1", "A2"]])
}

type T {
  # requires only A1 scope to access
  secret: String @requiresScopes(scopes: [["A1"]])
}

type T {
  # requires only A1 scope to access as A2 is redundant
  secret: String @requiresScopes(scopes: [["A1"]])
}
```

<!-- FED-853 -->

Partial backport of apollographql/federation#3321 and apollographql/federation#3343
dariuszkuc added a commit to apollographql/router that referenced this pull request Jan 22, 2026
Current merge policies for `@authenticated`, `@requiresScopes` and `@policy` were inconsistent.

If single subgraph declared a field with one of the directives then it would restrict access to this supergraph field regardless which subgraph would resolve this field (results in AND rule for any applied auth directive, i.e. `@authenticated` AND `@policy` is required to access this field). If the same auth directive (`@requiresScopes`/`@policy`) were applied across the subgraphs then the resulting supergraph field could be resolved by fullfilling either one of the subgraph requirements (resulting in OR rule, i.e. either `@policy` 1 or `@policy` 2 has to be true to access the field). While arguably this allowed for easier schema evolution, it did result in weakening the security requirements.

Since `@policy` and `@requiresScopes` values are represent boolean conditions in Disjunctive Normal Form, we can merge them conjunctively to get the final auth requirements, i.e.

```graphql
type T @authenticated {
  # requires scopes (A1 AND A2) OR A3
  secret: String @requiresScopes(scopes: [["A1", "A2"], ["A3"]])
}

type T {
  # requires scopes B1 OR B2
  secret: String @requiresScopes(scopes: [["B1"], ["B2"]]
}

type T @authenticated {
  secret: String @requiresScopes(
    scopes: [
      ["A1", "A2", "B1"],
      ["A1", "A2", "B2"],
      ["A3", "B1"],
      ["A3", "B2"]
    ])
}
```

This algorithm also deduplicates redundant requirements, e.g.

```graphql
type T {
  # requires A1 AND A2 scopes to access
  secret: String @requiresScopes(scopes: [["A1", "A2"]])
}

type T {
  # requires only A1 scope to access
  secret: String @requiresScopes(scopes: [["A1"]])
}

type T {
  # requires only A1 scope to access as A2 is redundant
  secret: String @requiresScopes(scopes: [["A1"]])
}
```

<!-- FED-853 -->

Partial backport of apollographql/federation#3321 and apollographql/federation#3343
dariuszkuc added a commit to apollographql/router that referenced this pull request Jan 30, 2026
Current merge policies for `@authenticated`, `@requiresScopes` and `@policy` were inconsistent.

If single subgraph declared a field with one of the directives then it would restrict access to this supergraph field regardless which subgraph would resolve this field (results in AND rule for any applied auth directive, i.e. `@authenticated` AND `@policy` is required to access this field). If the same auth directive (`@requiresScopes`/`@policy`) were applied across the subgraphs then the resulting supergraph field could be resolved by fullfilling either one of the subgraph requirements (resulting in OR rule, i.e. either `@policy` 1 or `@policy` 2 has to be true to access the field). While arguably this allowed for easier schema evolution, it did result in weakening the security requirements.

Since `@policy` and `@requiresScopes` values are represent boolean conditions in Disjunctive Normal Form, we can merge them conjunctively to get the final auth requirements, i.e.

```graphql
type T @authenticated {
  # requires scopes (A1 AND A2) OR A3
  secret: String @requiresScopes(scopes: [["A1", "A2"], ["A3"]])
}

type T {
  # requires scopes B1 OR B2
  secret: String @requiresScopes(scopes: [["B1"], ["B2"]]
}

type T @authenticated {
  secret: String @requiresScopes(
    scopes: [
      ["A1", "A2", "B1"],
      ["A1", "A2", "B2"],
      ["A3", "B1"],
      ["A3", "B2"]
    ])
}
```

This algorithm also deduplicates redundant requirements, e.g.

```graphql
type T {
  # requires A1 AND A2 scopes to access
  secret: String @requiresScopes(scopes: [["A1", "A2"]])
}

type T {
  # requires only A1 scope to access
  secret: String @requiresScopes(scopes: [["A1"]])
}

type T {
  # requires only A1 scope to access as A2 is redundant
  secret: String @requiresScopes(scopes: [["A1"]])
}
```

<!-- FED-853 -->

Partial backport of apollographql/federation#3321 and apollographql/federation#3343
dariuszkuc added a commit to apollographql/router that referenced this pull request Jan 30, 2026
Current merge policies for `@authenticated`, `@requiresScopes` and `@policy` were inconsistent.

If single subgraph declared a field with one of the directives then it would restrict access to this supergraph field regardless which subgraph would resolve this field (results in AND rule for any applied auth directive, i.e. `@authenticated` AND `@policy` is required to access this field). If the same auth directive (`@requiresScopes`/`@policy`) were applied across the subgraphs then the resulting supergraph field could be resolved by fullfilling either one of the subgraph requirements (resulting in OR rule, i.e. either `@policy` 1 or `@policy` 2 has to be true to access the field). While arguably this allowed for easier schema evolution, it did result in weakening the security requirements.

Since `@policy` and `@requiresScopes` values are represent boolean conditions in Disjunctive Normal Form, we can merge them conjunctively to get the final auth requirements, i.e.

```graphql
type T @authenticated {
  # requires scopes (A1 AND A2) OR A3
  secret: String @requiresScopes(scopes: [["A1", "A2"], ["A3"]])
}

type T {
  # requires scopes B1 OR B2
  secret: String @requiresScopes(scopes: [["B1"], ["B2"]]
}

type T @authenticated {
  secret: String @requiresScopes(
    scopes: [
      ["A1", "A2", "B1"],
      ["A1", "A2", "B2"],
      ["A3", "B1"],
      ["A3", "B2"]
    ])
}
```

This algorithm also deduplicates redundant requirements, e.g.

```graphql
type T {
  # requires A1 AND A2 scopes to access
  secret: String @requiresScopes(scopes: [["A1", "A2"]])
}

type T {
  # requires only A1 scope to access
  secret: String @requiresScopes(scopes: [["A1"]])
}

type T {
  # requires only A1 scope to access as A2 is redundant
  secret: String @requiresScopes(scopes: [["A1"]])
}
```

Partial backport of apollographql/federation#3321 and apollographql/federation#3343


Co-authored-by: Sachin D. Shinde <sachin@apollographql.com>
the-gigi-apollo pushed a commit to apollographql/router that referenced this pull request Feb 4, 2026
Current merge policies for `@authenticated`, `@requiresScopes` and `@policy` were inconsistent.

If single subgraph declared a field with one of the directives then it would restrict access to this supergraph field regardless which subgraph would resolve this field (results in AND rule for any applied auth directive, i.e. `@authenticated` AND `@policy` is required to access this field). If the same auth directive (`@requiresScopes`/`@policy`) were applied across the subgraphs then the resulting supergraph field could be resolved by fullfilling either one of the subgraph requirements (resulting in OR rule, i.e. either `@policy` 1 or `@policy` 2 has to be true to access the field). While arguably this allowed for easier schema evolution, it did result in weakening the security requirements.

Since `@policy` and `@requiresScopes` values are represent boolean conditions in Disjunctive Normal Form, we can merge them conjunctively to get the final auth requirements, i.e.

```graphql
type T @authenticated {
  # requires scopes (A1 AND A2) OR A3
  secret: String @requiresScopes(scopes: [["A1", "A2"], ["A3"]])
}

type T {
  # requires scopes B1 OR B2
  secret: String @requiresScopes(scopes: [["B1"], ["B2"]]
}

type T @authenticated {
  secret: String @requiresScopes(
    scopes: [
      ["A1", "A2", "B1"],
      ["A1", "A2", "B2"],
      ["A3", "B1"],
      ["A3", "B2"]
    ])
}
```

This algorithm also deduplicates redundant requirements, e.g.

```graphql
type T {
  # requires A1 AND A2 scopes to access
  secret: String @requiresScopes(scopes: [["A1", "A2"]])
}

type T {
  # requires only A1 scope to access
  secret: String @requiresScopes(scopes: [["A1"]])
}

type T {
  # requires only A1 scope to access as A2 is redundant
  secret: String @requiresScopes(scopes: [["A1"]])
}
```

Partial backport of apollographql/federation#3321 and apollographql/federation#3343


Co-authored-by: Sachin D. Shinde <sachin@apollographql.com>
briannafugate408 pushed a commit to apollographql/router that referenced this pull request Feb 4, 2026
Current merge policies for `@authenticated`, `@requiresScopes` and `@policy` were inconsistent.

If single subgraph declared a field with one of the directives then it would restrict access to this supergraph field regardless which subgraph would resolve this field (results in AND rule for any applied auth directive, i.e. `@authenticated` AND `@policy` is required to access this field). If the same auth directive (`@requiresScopes`/`@policy`) were applied across the subgraphs then the resulting supergraph field could be resolved by fullfilling either one of the subgraph requirements (resulting in OR rule, i.e. either `@policy` 1 or `@policy` 2 has to be true to access the field). While arguably this allowed for easier schema evolution, it did result in weakening the security requirements.

Since `@policy` and `@requiresScopes` values are represent boolean conditions in Disjunctive Normal Form, we can merge them conjunctively to get the final auth requirements, i.e.

```graphql
type T @authenticated {
  # requires scopes (A1 AND A2) OR A3
  secret: String @requiresScopes(scopes: [["A1", "A2"], ["A3"]])
}

type T {
  # requires scopes B1 OR B2
  secret: String @requiresScopes(scopes: [["B1"], ["B2"]]
}

type T @authenticated {
  secret: String @requiresScopes(
    scopes: [
      ["A1", "A2", "B1"],
      ["A1", "A2", "B2"],
      ["A3", "B1"],
      ["A3", "B2"]
    ])
}
```

This algorithm also deduplicates redundant requirements, e.g.

```graphql
type T {
  # requires A1 AND A2 scopes to access
  secret: String @requiresScopes(scopes: [["A1", "A2"]])
}

type T {
  # requires only A1 scope to access
  secret: String @requiresScopes(scopes: [["A1"]])
}

type T {
  # requires only A1 scope to access as A2 is redundant
  secret: String @requiresScopes(scopes: [["A1"]])
}
```

Partial backport of apollographql/federation#3321 and apollographql/federation#3343


Co-authored-by: Sachin D. Shinde <sachin@apollographql.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants