Skip to content

composition updates related to merging auth directives (backport)#3326

Merged
dariuszkuc merged 5 commits intoversion-2.10from
fed_2.10_auth
Nov 4, 2025
Merged

composition updates related to merging auth directives (backport)#3326
dariuszkuc merged 5 commits intoversion-2.10from
fed_2.10_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

Backport of #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.
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 16:11
@changeset-bot
Copy link

changeset-bot bot commented Nov 4, 2025

🦋 Changeset detected

Latest commit: b3ec52e

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 ready

The preview is ready to be viewed. View the preview

File Changes

0 new, 7 changed, 10 removed
* graphos/schema-design/federated-schemas/entities/contribute-fields.mdx
* graphos/schema-design/federated-schemas/entities/interfaces.mdx
* graphos/schema-design/federated-schemas/entities/intro.mdx
* graphos/schema-design/federated-schemas/entities/migrate-fields.mdx
* graphos/schema-design/federated-schemas/entities/use-contexts.mdx
* graphos/schema-design/federated-schemas/federation.mdx
* graphos/schema-design/federated-schemas/sharing-types.mdx
- graphos/schema-design/federated-schemas/reference/composition-rules.mdx
- graphos/schema-design/federated-schemas/reference/directives.mdx
- graphos/schema-design/federated-schemas/reference/errors.mdx
- graphos/schema-design/federated-schemas/reference/hints.mdx
- graphos/schema-design/federated-schemas/reference/query-plans.mdx
- graphos/schema-design/federated-schemas/reference/subgraph-spec.mdx
- graphos/schema-design/federated-schemas/reference/subgraph-specific-fields.mdx
- graphos/schema-design/federated-schemas/reference/versions.mdx
- graphos/schema-design/federated-schemas/reference/moving-to-federation-2.mdx
- graphos/schema-design/federated-schemas/reference/backward-compatibility.mdx

Build ID: 7dd97a33eb6399fee208f72a
Build Logs: View logs

URL: https://www.apollographql.com/docs/deploy-preview/7dd97a33eb6399fee208f72a

@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 c390861 into version-2.10 Nov 4, 2025
17 checks passed
@dariuszkuc dariuszkuc deleted the fed_2.10_auth branch November 4, 2025 16:27
dariuszkuc pushed 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 version-2.10, this
PR will be updated.


# Releases
## @apollo/composition@2.10.3

### Patch Changes

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

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`
([#3326](#3326))

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
    }
    ```

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

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
([#3326](#3326))

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"]])
    }
    ```

- Updated dependencies
\[[`2b88aec38d5bacb6ec815d885fdac47ef415124a`](2b88aec),
[`18a9cfaf533602bb37fdf22962539ce0eae948c8`](18a9cfa),
[`9c0aaa0874c98ae8ce0cc38cad7f6f25d2c29635`](9c0aaa0),
[`f94e7b35c43ed64c67ff25c7aeb86ec0dd73370a`](f94e7b3)]:
    -   @apollo/federation-internals@2.10.3
    -   @apollo/query-graphs@2.10.3

## @apollo/gateway@2.10.3

### Patch Changes

- Updated dependencies
\[[`2b88aec38d5bacb6ec815d885fdac47ef415124a`](2b88aec),
[`18a9cfaf533602bb37fdf22962539ce0eae948c8`](18a9cfa),
[`9c0aaa0874c98ae8ce0cc38cad7f6f25d2c29635`](9c0aaa0),
[`f94e7b35c43ed64c67ff25c7aeb86ec0dd73370a`](f94e7b3)]:
    -   @apollo/composition@2.10.3
    -   @apollo/federation-internals@2.10.3
    -   @apollo/query-planner@2.10.3

## @apollo/federation-internals@2.10.3

### Patch Changes

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

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`
([#3326](#3326))

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
    }
    ```

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

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
([#3326](#3326))

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.10.3

### Patch Changes

- Updated dependencies
\[[`2b88aec38d5bacb6ec815d885fdac47ef415124a`](2b88aec),
[`18a9cfaf533602bb37fdf22962539ce0eae948c8`](18a9cfa),
[`9c0aaa0874c98ae8ce0cc38cad7f6f25d2c29635`](9c0aaa0),
[`f94e7b35c43ed64c67ff25c7aeb86ec0dd73370a`](f94e7b3)]:
    -   @apollo/federation-internals@2.10.3

## @apollo/query-planner@2.10.3

### Patch Changes

- Updated dependencies
\[[`2b88aec38d5bacb6ec815d885fdac47ef415124a`](2b88aec),
[`18a9cfaf533602bb37fdf22962539ce0eae948c8`](18a9cfa),
[`9c0aaa0874c98ae8ce0cc38cad7f6f25d2c29635`](9c0aaa0),
[`f94e7b35c43ed64c67ff25c7aeb86ec0dd73370a`](f94e7b3)]:
    -   @apollo/federation-internals@2.10.3
    -   @apollo/query-graphs@2.10.3

## @apollo/subgraph@2.10.3

### Patch Changes

- Updated dependencies
\[[`2b88aec38d5bacb6ec815d885fdac47ef415124a`](2b88aec),
[`18a9cfaf533602bb37fdf22962539ce0eae948c8`](18a9cfa),
[`9c0aaa0874c98ae8ce0cc38cad7f6f25d2c29635`](9c0aaa0),
[`f94e7b35c43ed64c67ff25c7aeb86ec0dd73370a`](f94e7b3)]:
    -   @apollo/federation-internals@2.10.3

## apollo-federation-integration-testsuite@2.10.3

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