aws / aws-appsync-community

The AWS AppSync community
https://aws.amazon.com/appsync
Apache License 2.0
506 stars 32 forks source link

[Feature Request] Auth directives applied at the mutation, query, or subscription level should apply to all possible child/return types of those fields #266

Open tqhoughton opened 1 year ago

tqhoughton commented 1 year ago

Problem Statement

Currently, I can designate that users authenticating via a specific auth type should be able to have access to a specific query, for example:

type Post {
  id: ID!
  body: String!
}

type Query {
  getPostById: Post
  @aws_oidc @aws_api_key
}

One would think that this would mean that a user with that auth type would be able to invoke the query, however when you do so you get the following errors back:

{
  "data": {
    "getPostById": null
  },
  "errors": [
    {
      "path": [
        "getPostById",
        "body"
      ],
      "data": null,
      "errorType": "Unauthorized",
      "errorInfo": null,
      "locations": [
        {
          "line": 6,
          "column": 5,
          "sourceName": null
        }
      ],
      "message": "Not Authorized to access body on type Post"
    },
    {
      "path": [
        "getPostById",
        "id"
      ],
      "data": null,
      "errorType": "Unauthorized",
      "errorInfo": null,
      "locations": [
        {
          "line": 8,
          "column": 5,
          "sourceName": null
        }
      ],
      "message": "Not Authorized to access id on type Post"
    }
  ]
}

This is unintuitive and annoying because I would think that giving a user access to a query would allow it to successfully resolve that query, however this is not the case. Currently I would have to give each authentication type explicit access to every type that the query can return, which in this case is the Post type.

type Post @aws_api_key @aws_oidc {
  id: ID!
  body: String!
}
...

You might think, not so bad, right? Well what if we add another field to the Post that resolves another type down the road? For example, an author that resolves to a User.

type User {
  id: ID!
  name: String!
  email: AWSEmail
}

type Post @aws_api_key @aws_oidc {
  id: ID!
  body: String!
  author: User! # new field
}

This will break our API if users request any field on the author because we did not add another auth decorator to the User type. Consider that some queries may return deeply nested objects and this easily becomes a verbose nightmare to manage.

The current behavior makes defining multiple access modes on a single API extremely cumbersome because I can't just mark all queries accessible by aws_api_key and all mutations accessible by aws_oidc, for each query and mutation I have to explicitly mark every single return type and possible nested type as accessible to that authentication mode.

Proposal

My proposal is that each Object or Field type with an auth directive attached implicitly allows that type to return all child types that the field could possible return.

Under this proposal if I wanted to give all API Key auth requests access to all queries, I could just define one decorator on the Query type:

type Query {
   ...
}
@aws_api_key

And all of my api key requests can access ANY query. Same would be applied to mutations, subscriptions, and any type that can return nested types. If I wanted to mark a field as NOT accessible to an auth method I could always specify the allowed auth methods manually like so:

type Post {
  author: User!
  @aws_oidc # only allow oidc users to have access, not api_key users
}

I love the idea of using directives to control access, even if it requires you to be extremely verbose with defining access for every single type. However I believe that with a little bit more intuitive applications of those directives it could be a lot easier to use and allow for simpler schema definitions.