aws-amplify / amplify-cli

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development.
Apache License 2.0
2.81k stars 821 forks source link

@auth doesn't handle my permissions - what's the escape hatch? #1391

Closed plumdog closed 5 years ago

plumdog commented 5 years ago

Which Category is your question related to?

GraphQL, Auth

What AWS Services are you utilizing?

Amplify, (AppSync, Cognito)

Provide additional details e.g. code snippets


I think that my permissions model is beyond what @auth can handle. I have, in essence:

I need my permissions to be managed by users within the application (eg, an admin user says "you have access on project x and project y"), and on a per-project basis. I think that, in theory, this could work using Cognito groups, but I can't see how to say either:

So my assumption is: @auth is unable to handle my current situation (I'm aware #766 may handle this, but as far as I can tell doesn't handle it yet, and doesn't point me towards a solution. Is there I beta I can use?).


At any rate: assume I have some fiddly permissions model.

Does this mean Amplify is unable to accommodate my use case? Or is there a workaround? I noticed that https://aws.amazon.com/blogs/mobile/amplify-adds-support-for-multiple-environments-custom-resolvers-larger-data-models-and-iam-roles-including-mfa/ says:

When you’re developing GraphQL APIs, you often have to customize the resolvers to perform custom logic and implement things like data manipulation, authorization, or fine-grained access control.

implying that I can implement this kind of custom authorization in resolvers. Is this true? Is this a good idea?

Or can I write some kind of "call this Lambda function to decide if the current user can do the thing they want do do" code in my GraphQL somewhere?

My current inclination is to use GraphQL and @model and @searchable, but to make the GraphQL API totally private and only accessible from a Lambda that handles a REST API. (Eg, the frontend says GET /myapi/things?search=foo and my lambda makes some authorization decisions based on your Cognito user and some DynamoDB table rows controlling who can access what, and does some GraphQL at the private GraphQL API for you, eg "search Things matching foo where project in (projects you can access)"). But having to hide my GraphQL from my frontend client feels like it goes against the Amplify model, and makes me a bit sad. Is making my GraphQL API private actually possible with Amplify?

Much more generally, I love that Amplify is built with what it refers to as "escape hatches", but the docs haven't really helped me find one here.

Any help about how I might handle fine-grained access control would be appreciated.

mikeparisstuff commented 5 years ago

When building complex authorization use cases (such as ones that traverse multiple models) you have a few options.

1. Use a lambda function.

As of writing you can connect your own functions using custom stacks: https://aws-amplify.github.io/docs/cli/graphql#add-a-custom-resolver-that-targets-an-aws-lambda-function

We have a PR under review that simplifies this process into a @function directive #1346 that will be available as soon as it is merged.

2. Use pipeline functions.

You can use custom stacks to create AppSync pipeline functions that use multiple DynamoDB calls to authorize the action. For example you could get the thing's section then get the section's project and check if the user is allowed to view the project. If they are return the item, else return false. You can use custom resources to add the pipeline functions such that they are deployed via amplify. Here are the AppSync pipeline resolver docs https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html.

3. Store auth info on the Thing and use Elasticsearch.

If you store information such as the projectId on the Thing then you can use a pipeline resolver & elasticsearch to build authorization on top of rich text search. For example, you could use a field like this:

type Query {
  searchThingsInProject(projectId: String!, thingSearchTerm: String!): ThingConnection
}

Where your Query.searchThingsInProject resolver is a pipeline with two functions.

  1. Fetch the project by project id in $ctx.args.projectId and authorize the user.
  2. If the user is authorized, query elasticsearch and include the projectId in the query body as a term query (https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html). This will restrict only items that belong to this project to be searched and you have already determined that you are authorized for the project.
yuth commented 5 years ago

Feel free to comment if this doesn't answer your question

plumdog commented 5 years ago

@mikeparisstuff I think I understand the overall approach here - namely that the authorization happens "behind" GraphQL.

However, am I right that in doing so I would lose (or rather, would choose to ignore) the auto-generated resolvers that Amplify generates? I can't add my own function to the "front" of the generated resolver for each endpoint?

To put it another way: all of these approaches replace the resolvers, none of them let me augment the resolvers like @auth does?

Might my most effective way of doing this be:

github-actions[bot] commented 3 years ago

This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.

Looking for a help forum? We recommend joining the Amplify Community Discord server *-help channels for those types of questions.