Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorization directive docs #3449

Merged

Conversation

Meschreiber
Copy link
Contributor

@Meschreiber Meschreiber commented Jul 15, 2023

This PR rewrites the docs included in #3397.

To-do:

docs/source/config.json Outdated Show resolved Hide resolved
docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved

## Prequisites

To use the router's authorization directives, you need to either configure [JWT authentication](./authn-jwt) or add a [router service coprocessor](../customizations/coprocessor).
Copy link
Contributor

Choose a reason for hiding this comment

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

We should clarify which stage the coprocessor needs to be at to set these; guessing RouterService but would make sense to codify.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree, I had questions about this as well. Who would be best to consult with on this @lleadbet ?

Copy link
Contributor

Choose a reason for hiding this comment

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

@Geal most likely, but @chandrikas anyone else that would know?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, this should be in the router service. Let's also try and provide recipes here @Meschreiber so these prerequisites don't feel like barriers to adoption

docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved
docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved
docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved
docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved
docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved
docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved
docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved
docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved
Co-authored-by: Lucas Leadbetter <[email protected]>
@Meschreiber Meschreiber marked this pull request as ready for review July 20, 2023 17:22
@Meschreiber Meschreiber requested a review from a team as a code owner July 20, 2023 17:22
docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved
**Claims** are the individual details of a requests' scope. They might include details like the ID of the associated user and/or any scopes assigned to that user.

If you configure [JWT authentication](./authn-jwt), the Apollo Router automatically adds a JWT token's claims to the request's context at the `apollo_authentication::JWT::claims` key.
To participate in the authorization process, the coprocessor you add needs to set the key `apollo_authentication::JWT::claims` in request contexts.
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be good to have slightly more details (or a link) about this. https://www.apollographql.com/docs/router/configuration/authn-jwt/#example-throwing-errors-for-invalid-claims suggests that the value for the apollo_authentication::JWT::claims key is an array of strings, but in https://www.apollographql.com/docs/router/configuration/authn-jwt/#claim-augmentation-via-coprocessors it looks like an object. Which is it?

Copy link
Contributor

Choose a reason for hiding this comment

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

if claims == () || !claims.contains("iss") || claims["iss"] != "https://idp.local" { why does this look like an array of strings? Because it uses contains instead of something like contains_key?

Comment on lines 155 to 156
The directive validates the required scopes by loading the object at the `apollo_authentication::JWT::claims` key in a request's context.
That object's `scope` key should contain a space separated list of scopes in the format defined by the [OAuth2 RFC for access token scopes](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3).
Copy link
Contributor

Choose a reason for hiding this comment

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

"Object" suggests a JSON object (map) but "list" suggests an array. Again, which is it?

Copy link
Contributor

Choose a reason for hiding this comment

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

claims = context["apollo_authentication::JWT::claims"] is an object
claims["scope"] is a string with this format: scope1 scope2 scope3

docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved
**Claims** are the individual details of a requests' scope. They might include details like the ID of the associated user and/or any scopes assigned to that user.

If you configure [JWT authentication](./authn-jwt), the Apollo Router automatically adds a JWT token's claims to the request's context at the `apollo_authentication::JWT::claims` key.
To participate in the authorization process, the coprocessor you add needs to set the key `apollo_authentication::JWT::claims` in request contexts.
Copy link
Contributor

Choose a reason for hiding this comment

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

if claims == () || !claims.contains("iss") || claims["iss"] != "https://idp.local" { why does this look like an array of strings? Because it uses contains instead of something like contains_key?

Comment on lines 155 to 156
The directive validates the required scopes by loading the object at the `apollo_authentication::JWT::claims` key in a request's context.
That object's `scope` key should contain a space separated list of scopes in the format defined by the [OAuth2 RFC for access token scopes](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3).
Copy link
Contributor

Choose a reason for hiding this comment

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

claims = context["apollo_authentication::JWT::claims"] is an object
claims["scope"] is a string with this format: scope1 scope2 scope3

docs/source/configuration/authorization.mdx Outdated Show resolved Hide resolved
docs/source/configuration/authorization.mdx Show resolved Hide resolved
@Geal Geal self-requested a review August 9, 2023 09:07
@Geal
Copy link
Contributor

Geal commented Aug 9, 2023

maybe https://www.apollographql.com/docs/router/configuration/authn-jwt/#claim-augmentation-via-coprocessors should be moved to this part of the documentation

@Geal
Copy link
Contributor

Geal commented Aug 10, 2023

@Meschreiber I think we can merge that in the authorization PR now. Then I'll update and merge the @policy PR in too, and we can look again at the docs once the last details are settled

@Geal
Copy link
Contributor

Geal commented Aug 10, 2023

on second thought: we leave this PR up for reviews for now, I'll add a section about @policy, and merge the @policy PR with the authorization PR (and hopefully in the process, not create conflicts everywhere)

@Geal
Copy link
Contributor

Geal commented Aug 11, 2023

@Meschreiber I added docs for @policy. Do you think it makes sense to add an example? It would be very similar to @requiresScopes


APIs provide access to business-critical data. Unrestricted access can result in data breaches, monetary losses, or potential denial of service. Even for internal APIs, checks can be essential to limit data to authorized parties.

Enforcing authorization before processing requests is more efficient because it allows for early request termination. It also enhances security by creating an initial checkpoint that can be reinforced in other service layers.
Copy link
Contributor

Choose a reason for hiding this comment

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

We want to weave in "defense-in-depth" if possible


<ExpansionPanel title="Click to expand">

The router level coprocessor is guaranteed to be called after the authentication plugin, so the coprocessor can receive the list of claims extracted from the token, use information like the `sub` (subject) claim to look up the user, insert its data in the claims list and return it to the router.
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding this concrete note about order of plugins!

@policy(policies: ["claims[`roles`].contains(`support`)"])
```

The Apollo Router extract from the schema the list of policies relevant to the query, and stores them in the request's context, under the key `apollo_authorization::policies::required` as a map `policy -> null|true|false`. This is done at the [Router service level](../customizations/overview#the-request-lifecycle). A Rhai script or a coprocessor at the Supergraph service level goes through this map, setting the value to `true` if they are successful and `false` if not. After that, the router will filter types and fields for which the policies failed or were not executed.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The Apollo Router extract from the schema the list of policies relevant to the query, and stores them in the request's context, under the key `apollo_authorization::policies::required` as a map `policy -> null|true|false`. This is done at the [Router service level](../customizations/overview#the-request-lifecycle). A Rhai script or a coprocessor at the Supergraph service level goes through this map, setting the value to `true` if they are successful and `false` if not. After that, the router will filter types and fields for which the policies failed or were not executed.
The Apollo Router extracts from the schema the list of policies relevant to the query, and stores them in the request's context, under the key `apollo_authorization::policies::required` as a map `policy -> null|true|false`. This is done at the [Router service level](../customizations/overview#the-request-lifecycle). A Rhai script or a coprocessor at the Supergraph service level goes through this map, setting the value to `true` if they are successful and `false` if not. After that, the router will filter types and fields for which the policies failed or were not executed.

this cannot work yet because router service level scripts don't work yet
@Geal
Copy link
Contributor

Geal commented Aug 11, 2023

I'm adding references to work that is in progress or soon to be merged, but will be useful for authz:

@Meschreiber Meschreiber merged commit b9a5116 into geal/authorization-directives Aug 11, 2023
@Meschreiber Meschreiber deleted the ms/authorization-directives-docs branch August 11, 2023 22:30
You define and use these directives on subgraph schemas, and GraphOS [composes](#composition-and-federation) them onto the supergraph schema.
The router then enforces these directives on all incoming requests.

## Prerequisites
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we simply the prerequisites section? List the two simple options

  • JWT Authentication configuration
  • Claims extraction and augmentation via coprocessors

And link them to their description sections elsewhere. Right now I feel like I need to read through a lot before I can get to the exciting bit

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I'm not sure putting them on a different page or at the end of the article would make a lot of sense.

We can put them in Expansion Blocks so they're "hidden" and users have to click to expand. Let me know if you think that's insufficient.

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.

5 participants