Skip to content

access control validation and merge logic updates#3343

Merged
dariuszkuc merged 4 commits intomainfrom
fed_2.12.1_fixes
Nov 13, 2025
Merged

access control validation and merge logic updates#3343
dariuszkuc merged 4 commits intomainfrom
fed_2.12.1_fixes

Conversation

@dariuszkuc
Copy link
Member

No description provided.

dariuszkuc and others added 4 commits November 10, 2025 12:12
Fixed access control verification of transitive requirements (through `@requires` and/or `@fromContext`) to ensure it works with chains of transitive dependencies.
Update composition logic to allow specifying access control directives (`@authenticated`, `@requiresScopes` and `@policy`) on `@interfaceObject` fields.

While we disallow access control on interface types and fields, we decided to support it on `@interfaceObject` as it is a useful pattern to define a single resolver (that may need access controls) for common interface fields. Alternative would require our users to explicitly define resolvers for all implementations which defeats the purpose of `@interfaceObject`.

This PR refactors in how we propagate access control by providing additional merge sources when merging directives on interfaces, interface fields and object fields.
Fixes:
* we now correctly sort all conditions so we can properly deduplicate
entries in `dnfConjunction`
* added extra type checks when validating usage of access control on
interfaces
* fixed `AuthRequirementsOnElement.isSubset()` to verify that
`@requires`/`@fromContext` field auth requirements satisfy transitive
requirements
* fixed `AuthRequirements.satisfies()` logic to use DNF conjunction of
auth requirements from type and field to get the final requirements to
validate against
* cleaned up computation of additional access control sources
* fixed handling of renamed access control directives
* added comments around our usage of additional sources for access
control
* updated logic to handle `@interfaceObject` -> object type -> other
interface access control propagation chains
* `copyNonJoinAppliedDirectives()` was updated to pull additional
sources from `accessControlAdditionalSources()` for auth directives,
instead of just copying the interface field directives (which were
already merged)
* update DNF handling logic to convert empty `[]` into `[[]]` to ensure
we correctly calculate conjunctions for empty cases
* update `dnfConjunction()` to copy its inputs as it mutates the values
when sorting

---------

Co-authored-by: Sachin D. Shinde <sachin@apollographql.com>
`@listSize` validations were not correctly unwrapping non-nullable
composite types and fields.
@dariuszkuc dariuszkuc requested a review from a team as a code owner November 13, 2025 15:16
@changeset-bot
Copy link

changeset-bot bot commented Nov 13, 2025

🦋 Changeset detected

Latest commit: ac1ed29

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

@codesandbox-ci
Copy link

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 69eff4a into main Nov 13, 2025
16 checks passed
@dariuszkuc dariuszkuc deleted the fed_2.12.1_fixes branch November 13, 2025 15:18
dariuszkuc pushed a commit that referenced this pull request Nov 13, 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 main, this PR will
be updated.


# Releases
## @apollo/composition@2.12.1

### Patch Changes

- Fixed access control verification of transitive requirements (through
`@requires` and/or `@fromContext`) to ensure it works with chains of
transitive dependencies.
([#3343](#3343))

- Allow interface object fields to specify access control
([#3343](#3343))

Update composition logic to allow specifying access control directives
(`@authenticated`, `@requiresScopes` and `@policy`) on
`@interfaceObject` fields. While we disallow access control on interface
types and fields, we decided to support it on `@interfaceObject` as it
is a useful pattern to define a single resolver (that may need access
controls) for common interface fields. Alternative would require our
users to explicitly define resolvers for all implementations which
defeats the purpose of `@interfaceObject`.

This PR refactors in how we propagate access control by providing
additional merge sources when merging directives on interfaces,
interface fields and object fields.

- Updated dependencies
\[[`09e596e6a0c753071ca822e84f525d73ada395cf`](09e596e),
[`ac1ed2946c48e0fef4b413b192d8c5fbdb2370ae`](ac1ed29)]:
    -   @apollo/federation-internals@2.12.1
    -   @apollo/query-graphs@2.12.1

## @apollo/gateway@2.12.1

### Patch Changes

- Updated dependencies
\[[`b19431e4a92206703e29aba859a5fc7574b9ef8b`](b19431e),
[`09e596e6a0c753071ca822e84f525d73ada395cf`](09e596e),
[`ac1ed2946c48e0fef4b413b192d8c5fbdb2370ae`](ac1ed29)]:
    -   @apollo/composition@2.12.1
    -   @apollo/federation-internals@2.12.1
    -   @apollo/query-planner@2.12.1

## @apollo/federation-internals@2.12.1

### Patch Changes

- Allow interface object fields to specify access control
([#3343](#3343))

Update composition logic to allow specifying access control directives
(`@authenticated`, `@requiresScopes` and `@policy`) on
`@interfaceObject` fields. While we disallow access control on interface
types and fields, we decided to support it on `@interfaceObject` as it
is a useful pattern to define a single resolver (that may need access
controls) for common interface fields. Alternative would require our
users to explicitly define resolvers for all implementations which
defeats the purpose of `@interfaceObject`.

This PR refactors in how we propagate access control by providing
additional merge sources when merging directives on interfaces,
interface fields and object fields.

- Fixed demand control validations to unwrap non-nullable composite
types and fields when performing validations.
([#3343](#3343))

## @apollo/query-graphs@2.12.1

### Patch Changes

- Updated dependencies
\[[`09e596e6a0c753071ca822e84f525d73ada395cf`](09e596e),
[`ac1ed2946c48e0fef4b413b192d8c5fbdb2370ae`](ac1ed29)]:
    -   @apollo/federation-internals@2.12.1

## @apollo/query-planner@2.12.1

### Patch Changes

- Updated dependencies
\[[`09e596e6a0c753071ca822e84f525d73ada395cf`](09e596e),
[`ac1ed2946c48e0fef4b413b192d8c5fbdb2370ae`](ac1ed29)]:
    -   @apollo/federation-internals@2.12.1
    -   @apollo/query-graphs@2.12.1

## @apollo/subgraph@2.12.1

### Patch Changes

- Updated dependencies
\[[`09e596e6a0c753071ca822e84f525d73ada395cf`](09e596e),
[`ac1ed2946c48e0fef4b413b192d8c5fbdb2370ae`](ac1ed29)]:
    -   @apollo/federation-internals@2.12.1

## apollo-federation-integration-testsuite@2.12.1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

1 participant