-
Notifications
You must be signed in to change notification settings - Fork 273
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
Authorization directive docs #3449
Conversation
|
||
## 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). |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
Co-authored-by: Lucas Leadbetter <[email protected]>
…/apollographql/router into ms/authorization-directives-docs
**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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
?
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). |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
**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. |
There was a problem hiding this comment.
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
?
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). |
There was a problem hiding this comment.
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
Co-authored-by: Simon Sapin <[email protected]>
maybe https://www.apollographql.com/docs/router/configuration/authn-jwt/#claim-augmentation-via-coprocessors should be moved to this part of the documentation |
@Meschreiber I think we can merge that in the authorization PR now. Then I'll update and merge the |
on second thought: we leave this PR up for reviews for now, I'll add a section about |
@Meschreiber I added docs for |
|
||
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
I'm adding references to work that is in progress or soon to be merged, but will be useful for authz: |
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
This PR rewrites the docs included in #3397.
To-do: