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

@auth Combining Owner/Groups rules for Multi-Tenant Apps #449

Open
lennybr opened this issue Oct 18, 2018 · 90 comments
Open

@auth Combining Owner/Groups rules for Multi-Tenant Apps #449

lennybr opened this issue Oct 18, 2018 · 90 comments

Comments

@lennybr
Copy link

lennybr commented Oct 18, 2018

Is your feature request related to a problem? Please describe.
Ability to support multi-tenancy thru AppSync where individual items are "owned/belong" to a tenant instead of a user and we still have the ability to permission queries and mutations. Generated resolvers today effectively use isOwner || isInGroup(x for x in cognitoGroups) logic so multiple @auth rules cannot be combined to create more granular permissions.

Describe the solution you'd like
A few ideas:

  • Provide the ability to declare the combination logic before transformation so we could generate isOwner && isInGroup(x for x in cognitoGroups) when we have both rules types declared
  • Create a new @auth tenant strategy which uses the existing ownership transformation code behind the scenes but automatically changes the combination logic to isTenant && (isOwner || isInGroup(x for x in cognitoGroups))

Describe alternatives you've considered
Currently using the existing @auth owner strategy with custom ownerField and identityFIeld values, and setting the tid claim on the token with a pre-token generation Lambda function:

@auth(rules: [{allow: owner, ownerField: "tid", identityField: "claims.tid"}])

When used as the only @auth strategy, it works as intended (e.g. inserting the correct tid value during mutations; filters by tid value during queries, etc.).

But when I combine with @auth static groups strategy for permissions, the authorisation checks use OR logic instead of AND logic. I can't check for instance that a record both belongs to Tenant A (which the user belongs to) and has Permission X.

@hisham
Copy link

hisham commented Oct 18, 2018

Probably related to aws-amplify/amplify-cli#305 as well.

@learningacct
Copy link

This would also at least partially answer the question I posted over on amplify-js:
aws-amplify/amplify-js#1926

@mikeparisstuff
Copy link
Contributor

In regards to aws-amplify/amplify-cli#305, that issue has been fixed here aws-amplify/amplify-cli#285. You may see the PR notes to see what has changed as well as an example of the complex auth flows that are possible now.

@mikeparisstuff
Copy link
Contributor

mikeparisstuff commented Oct 19, 2018

In regards to this issue, it seems like the question relates to how to configure advanced auth flows such that rules may be composed together using "and" and "or" in flat and nested configurations.

Since this is really a feature request, let's try to define the ideal implementation. To allow for this use case we can extend the @auth directive definition to be the following:

directive @auth(rules: [AuthRule!]!) on OBJECT
input AuthRule {
    allow: AuthStrategy!
    ownerField: String # defaults to "owner"
    identityField: String # defaults to "username"
    groupsField: String
    groups: [String]
    queries: [ModelQuery]
    mutations: [ModelMutation]

    # New Additions
    and: [AuthRule]
    or: [AuthRule]
}
enum AuthStrategy { owner groups }
enum ModelQuery { get list }
enum ModelMutation { create update delete }

This follows the same structure that is used when we generate filter input objects for @model and @searchable types. The self-recursive structure allows nesting rules and combining logic with different boolean operators.

An example:

@auth(rules: [
    { and: [
        { allow: owner } # Call this A
        { or: [
            { allow: groups, groups: ["Admin"] } # Call this B
            { allow: groups, groupsField: "groups"] } # Call this C
        ]}
    ]}
])

would result in a final boolean expression:

A and (B or C)

Conceptually this makes sense and should be possible but I will have to spend more time with the details to figure out if there are any scenarios that will not work. If you have any feedback on this design please share as it would be helpful to have a few alternative to help guide the discussion.

Notes:

  • The queries & mutations argument would only be respected on the top level rule. i.e. a nested rule such as { and: [{allow: owner, queries: null}] } will ignore the queries: null as it is only valid at the top level.
  • Providing multiple top level rules will continue to be joined by an OR. If you require only AND then use a single rule with the and key filled out.

@lennybr
Copy link
Author

lennybr commented Oct 20, 2018

This would definitely solve this specific use case and opens up many more complex auth options.

To solve the multi-tenant use case the auth rule becomes:

@auth(rules: [
    { and: [
        { allow: owner, ownerField: "tid", identityField: "claims.tid" } # Call this TenantId
        { or: [
            { allow: groups, groups: ["Admin"] } # Call this B
            { allow: groups, groupsField: "groups"] } # Call this C
        ]}
    ]}
])

would result in a final boolean expression:

TenantId and (B or C)

This would opaquely add/match the TenantId value during any mutations and filter any queries based on the TenantId. The subset of other auth rules would then further control access.

Happy to help test out various other combinations to validate this strategy either on the branch your are using for PR aws-amplify/amplify-cli#183 or if you want to handle this separately.

@learningacct
Copy link

To clarify, as @auth uses Cognito groups rather than something like a members: [ID] field in the schema, is there any way to use this functionality to create smaller groups within a tenant? It seems like the best solution I could come up is overly complex and adds considerable Cognito bloat.

@lennybr, I think an alternative to a new tenant rule (and a solution for a lot of the complexity I'm trying to work around) could be allowing @auth to pull tenant/group information from the schema rather than pushing it all to Cognito. I admittedly know too little about this to know if that would be breaking best practices (if it even worked in the first place), but it seems like that would enable a lot of functionality without having to build new custom auth rules for every use case.

@lennybr
Copy link
Author

lennybr commented Oct 20, 2018

The queries and mutations argument only working on the top-level wouldn’t let us create a multi-tenant setup, since those arguments would be for each group in theory, I would want to do something like:

@auth(rules: [
    { and: [
        { allow: owner, ownerField: "tid", identityField: "claims.tid" } # Call this TenantId
        { or: [
            { allow: groups, groups: ["Admin"] }
            { allow: groups, groupsField: "readerGroups"], mutations: null }
            { allow: groups, groupsField: "editorGroups"], mutations: [update] }
        ]}
    ]}
])

@lennybr
Copy link
Author

lennybr commented Oct 23, 2018

@learningacct interesting idea, might be able to accomplish that by creating a Tenant model and wrapping any other tenant-separated models with a nested resolver that first does a user/tenant lookup and then passes down the Tenant Id to the actual tenant model data resolver. There would likely be performance implications running both resolvers, so not sure about practicality. And linked Cognito Users to Tenants would be much easier after aws-amplify/amplify-cli#56 is solved.

A schema could look something like:

type TenantMembership @model {
  id: UserId!
  tid: Tenant!
}

type Data___Tenant @model {
    id: ID!
    data: Data
}

type Data @model(queries: null) {
  id: ID!
  tid: Tenant!
  content: String
}

# Queries would point to the ___Tenant model instead
type Query {
    getData(id: ID!): Data___Tenant
}

and the Data___Tenant resolvers

# Request template looks up the tenant Id for the current user:
{ 
  "version": "2017-02-28",
  "operation": "GetItem",
  "key": {
    "id": $util.dynamodb.toDynamoDBJson($ctx.identity.username),
  }
}

# Response template returns that Tenant Id:
$util.toJson($context.result.tid)

and the Data resolvers

# Request template looks up data, but also inherits $ctx.source with the Tenant Id context:
{ 
  "version": "2017-02-28",
  "operation": "GetItem",
  "key": {
    "id": $util.dynamodb.toDynamoDBJson($ctx.args.id),
  }
}

# Response template can check tenant authorisation:
## START: Validate Tenant. **
#if( $ctx.result.tid == $ctx.source.tid )
  #set($isTenantAuthorized = true)
#end
## END: Validate Tenant. **

## START: Throw if Unauthorized. **
#if( !$isTenantAuthorized )
  $util.unauthorized()
#end
## END: Throw if Unauthorized. **

$util.toJson($context.result)

@kaustavghosh06 kaustavghosh06 added the feature-request New feature or request label Oct 23, 2018
@mikeparisstuff
Copy link
Contributor

@learningacct As of aws-amplify/amplify-cli#285 you can use the multiple owner auth features to support the members: [ID] use case:

For example,

type Team @model @auth(rules: [{ allow: owner, ownerField: "members" }]) {
  id: ID!
  name: String
  members: [ID]
}

This would only allow team members (as defined by the list of ids in members) to CRUDL the team object.

@mikeparisstuff
Copy link
Contributor

Also this discussion might be relevant to this aws-amplify/amplify-cli#318. Here we talk about how you can use the schema to build complex relationships that also effectively act as auth rules for read operations. There are some implications of using this to protect mutations but it may offer some new ideas.

@learningacct
Copy link

learningacct commented Oct 23, 2018

Ah, great! I did try to look through that PR, but a lot of it went over my head. Thank you! And aws-amplify/amplify-cli#318 looks like it provides some helpful examples too. I'll look that over and do my best to apply it to what I'm doing. :)

@mikeparisstuff, am I able to use a variation of aws-amplify/amplify-cli#318 to sort of nest tasks within teams within tenants?

@lennybr
Copy link
Author

lennybr commented Oct 31, 2018

@mikeparisstuff aws-amplify/amplify-cli#318 seems messy, especially with mutations and filling in owner fields. And while the members: [ID] use case works well to protect a single model, we are still missing the ability to protect related models. Any thoughts on the approach for that use case?

@mikeparisstuff
Copy link
Contributor

@lennybr You are correct and that is actually the plan as soon as the feature is possible in AppSync. We are going to add new strategies for auth rules and one of them will be to check for membership in a many-to-many connection but this is blocked by a service feature that will be releasing later this year. A similar ask has been made in aws-amplify/amplify-cli#372.

@lennybr
Copy link
Author

lennybr commented Nov 13, 2018

@mikeparisstuff great news. Any insights into the approach you are going to take for implementation? Will AppSync support middleware we can auth for Auth? Will you use nested resolvers? Or something else? I'm working on my own implementation now (currently using a root nested resolver against a tenantMembership object) but would love to stay in sync and align. Let us know if we can help and where to track progress on the feature. Thanks!

@hisham
Copy link

hisham commented Nov 23, 2018

Pipeline resolvers just got released (https://aws.amazon.com/blogs/mobile/aws-appsync-releases-pipeline-resolvers-aurora-serverless-support-delta-sync/) and I assume this is the feature @mikeparisstuff is referring to. Looks very interesting! :)

@mikeparisstuff
Copy link
Contributor

@hisham 👍 Exactly correct. We have a few changes that need to be made first but this is on the roadmap. Unfortunately no set date but I will reference these issues as soon as its in PR.

@sollipse
Copy link

sollipse commented Jan 2, 2019

Just chiming in to say that our team is also eagerly awaiting this feature.

@nagey
Copy link

nagey commented Jan 3, 2019

@mikeparisstuff pipeline resolvers seem like a great step on this.

building an app using amplify right now (which we're finding super promising), and sorely need this feature. Should we bite the bullet and write our own pipeline resolvers, or should we expect to see this via the CLI soon?

@amirmishani
Copy link

@mikeparisstuff until this issue is solved, what is currently the best approach to a complex multi tenant auth where you need to check again both tenant_id and user's role? A custom resolver using VTL for every table that needs auth?

@sebastienfi
Copy link

sebastienfi commented May 2, 2019

@lennybr said:

Currently using the existing @auth owner strategy with custom ownerField and identityFIeld values, and setting the tid claim on the token with a pre-token generation Lambda function:

@auth(rules: [{allow: owner, ownerField: "tid", identityField: "claims.tid"}])

...and later, @lennybr said:

To solve the multi-tenant use case the auth rule becomes:

@auth(rules: [
    { and: [
        { allow: owner, ownerField: "tid", identityField: "claims.tid" } # Call this TenantId
        { or: [
            { allow: groups, groups: ["Admin"] } # Call this B
            { allow: groups, groupsField: "groups"] } # Call this C
        ]}
    ]}
])

...and later, @mikeparisstuff said in https://github.com/aws-amplify/amplify-cli/issues/1043:

Proposals 1 & 5 implemented in aws-amplify/amplify-cli#1262

⚡️🚀⚡️

Proposal 5: And/Or in @auth rules

Github Issues

Problem

Currently all @auth rules are joined via a top level OR operation. For example, the schema below results in rules where you can access Post objects if you are the owner OR if you are member of the "Admin" group.

type Post @model @auth(rules: [{ allow: owner }, { allow: groups, groups: ["Admin"] }]) {
    id: ID!
    title: String
    author: User @connection(name: "UserPosts")
    owner: String
}

It would be useful if you could organize these auth rules using more complex rules combined with AND and OR.

Solution

We can accomplish this by adding to the the @auth definition.

directive @auth(rules: [TopLevelAuthRule!]!) on OBJECT, FIELD_DEFINITION
input TopLevelAuthRule {
    # For backwards compat, any rule specified at the same level as an "and"/"or" will be joined via an OR.
    allow: AuthStrategy!
    ownerField: String # defaults to "owner"
    identityField: String # defaults to "cognito:username" for UserPools, "username" for IAM, "sub" for OIDC
    groupsField: String
    groups: [String]
    
    # This only exists in top level rules and specifies operations for all the rules even when combined with and/or.
    # Neseted "operations" tags are not allowed because it would confuse evaluation logic.
    operations: [ModelOperation]

    # New recursive fields on AuthRule
    and: [AuthRule]
    or: [AuthRule]   
}
input AuthRule {
    allow: AuthStrategy!
    ownerField: String # defaults to "owner"
    identityField: String # defaults to "cognito:username" for UserPools, "username" for IAM, "sub" for OIDC
    groupsField: String
    groups: [String]

    # New recursive fields on AuthRule
    and: [AuthRule]
    or: [AuthRule]
}
enum AuthStrategy { owner groups }
# Reduces get/list to read. See explanation below.
enum ModelOperation { create update delete read }

This would allow users to define advanced auth configurations like:

type User
  @model 
  @auth(rules: [{
    and: [
      { allow: owner },
      { or: [
        { allow: groups, groups: ["Admin"] },
        { allow: owner, ownerField: ["admins"] },
      }
    ],
    operations: [read]
  }]) {
  id: ID!
  admins: [String]
  owner: String
}
# Logically: ( isOwner && ( isInAdminGroup || isMemberOfAdminsField ) )

The generated resolver logic will need to be updated to evaluate the expression tree.

@lennybr It seems that the solution you projected is now a reality. A solution for setting the tid claim on the token with a pre-token generation Lambda function would be something like

exports.handler = (event, context, callback) => {

  console.log(
    'Received eventObject {} in Invoke Request.',
    JSON.stringify(event, 3),
  );

  event.response = {
      "claimsOverrideDetails": {
          "claimsToAddOrOverride": {
              "tid": event.userPoolId,
          },
      }
  };

  // Return to Amazon Cognito
  callback(null, event);
};

That would kick-start any multi-tenant Amplify project. Comprehensive docs are here. Is that how you are doing this?

@sebastienfi
Copy link

sebastienfi commented May 2, 2019

I got mistaken, @mikeparisstuff meant "Proposals 1 & 4 implemented", so and and or operators are nowhere to be found in the code yet.

Lambda config itself does not allow setting the pre-token generation lambda trigger (and amplify-cli does not allow to set any lambda trigger...) so setting the trigger on the User Pool remains a manual action.

@nhruch
Copy link

nhruch commented Apr 12, 2022

@paulsson I have the same question as @zepelega , where did this API call come from?

Separately, how is this solution possible when you have a circular dependency between Amplify's Auth (pre token lambda) and API (table that stores group). In order to create the Auth module with a pre token generation lambda, you have to give it access to the API through IAM. In order to set up the API with an IAM auth rule, you need an Auth module that depends on having a pre token generation lambda with API access. Trying to accomplish this via the Amplify CLI returns a circular dependency error.

@paulsson
Copy link

@zepelega api.gqlCall is just some shared common code that I wrote that is used in all my lambdas/code that needs to make GraphQL/AppSync API calls. Nothing fancy.

@nhruch here is another GitHub issue where I detailed how to break the circular dependency:
aws-amplify/amplify-cli#1874 (comment)

@paulsson
Copy link

@nhruch looks like Amplify CLI added built-in functionality to break these circular dependencies.
aws-amplify/amplify-cli#4568 (comment)

@dlamon1
Copy link

dlamon1 commented Jun 9, 2022

Any update on this?

@dlamon1
Copy link

dlamon1 commented Jun 14, 2022

The following is a working Lambda that includes @paulsson 's code with the addition of an API call to graphql. This works by adding "type": "module" to package.json as well as removing the loader function in function/src/index.js and replacing it with this code. Run amplify function update, select Resource access permissions, and add api.

import crypto from '@aws-crypto/sha256-js';
import { defaultProvider } from '@aws-sdk/credential-provider-node';
import { SignatureV4 } from '@aws-sdk/signature-v4';
import { HttpRequest } from '@aws-sdk/protocol-http';
import fetch from "node-fetch";
import { Request as Request } from "node-fetch";

const { Sha256 } = crypto;

// use your own endpoint here
const GRAPHQL_ENDPOINT = <YOUR_API_ENDPOINT>;
const AWS_REGION = process.env.AWS_REGION || 'us-east-2';

const query = `query GetUser(
  $id: ID!
) {
  getUser(id: $id) {
    role
    business {
      id
      locations(limit: 100) {
        items {
          id
        }
      }
    }
  }
}`;

export async function handler(event) {

  // get the user ID (Cognito sub)
  const userSub = event.request.userAttributes.sub;

  // get the user groups assigned through Cognito groups
  const groups = event.request.groupConfiguration.groupsToOverride;

  // Return early if user is admin, will have full auth access anyway
  if (groups.includes('admin')) { return event }

  const endpoint = new URL(GRAPHQL_ENDPOINT);

  const signer = new SignatureV4({
    credentials: defaultProvider(),
    region: AWS_REGION,
    service: 'appsync',
    sha256: Sha256
  });

  const requestToBeSigned = new HttpRequest({
    method: 'POST',
    headers: {
      host: endpoint.host
    },
    hostname: endpoint.host,
    body: JSON.stringify({ query: query, variables: { id: userSub } }),
    path: endpoint.pathname
  });

  const signed = await signer.sign(requestToBeSigned);
  const request = new Request(endpoint, signed);

  let statusCode = 200;
  let body;
  let response;

  try {
    response = await fetch(request);
    body = await response.json();
    if (body.errors) statusCode = 400;
  } catch (error) {
    statusCode = 400;
    body = {
      errors: [
        {
          status: response.status,
          message: error.message,
          stack: error.stack
        }
      ]
    };

    return event
  }

  // Return if no user is found in DB, handle this case
  if (!body.data.getUser) return event

  const claimsToAddOrOverride = {}
  const customGroups = [];

  const businessId = body.data.getUser?.business?.id

  if (businessId) {
    claimsToAddOrOverride.business_id = businessId
  }

  if (body.data.getUser?.role === "BUSINESS_ADMIN") {
    customGroups.push(businessId);

    // add all Business Location IDs to customGroups
    body.data.getUser?.business?.locations?.items.forEach((item) => {
      customGroups.push(item.location.id);
    });

  } else if (body.data.getUser.role === "LOCATION_ADMIN") {

    // add only the specific Location IDs that the user is assigned to
    body.data.getUser?.locations?.items.forEach((item) => {
      customGroups.push(item.location.id);
    });

    claimsToAddOrOverride.locationAdmin = businessId;

  } else {

    claimsToAddOrOverride.readAccess = businessId;
  }

  event.response = {
    claimsOverrideDetails: {
      groupOverrideDetails: {
        groupsToOverride: [...groups, ...customGroups],
      },
      claimsToAddOrOverride,
    }
  };

  return event;
}

Another hurdle as of CLI v8.5.0 is V2 of graphql. There is a breaking change where the auth rule doesn't like to read from the id's. I've changed my structure to the following and in the front end pass in the same UUID to both id and authid

type Business
  @model
  @auth(
    rules: [
      # grants "root" admins CRUD operations on all Businesses
      {
        allow: groups
        groups: ["admin"]
        operations: [create, read, update, delete]
      }
      # grants Business admins read, update operations if the calling user is an employee of (assigned to) the Business
      { allow: groups, groupsField: "authid", operations: [read, update] }
      # grants Location admins read access if the calling user is an employee of (assigned to) the Business
      {
        allow: groups
        groupsField: "authid"
        groupClaim: "locationAdmin"
        operations: [read]
      }
    ]
  ) {
  id: ID!
  authid: String!
  name: String!
  locations: [Location] @hasMany(indexName: "byBusiness", fields: ["id"])
}

@BBopanna
Copy link

BBopanna commented Jul 6, 2022

AWS team - this feature of supporting multi tenancy is critical for enterprise grade apps. Cant believe this is open from 2018 and no action yet!! @dabit3 @kaustavghosh06 can you please help promote this ? also this is marked P4 ?!!!??? damn this is P1.

@isaiahtaylor
Copy link

Subscription tiers is another use case for this. Very difficult to model different types of permissions for different users without logical AND.

@Crusade1337
Copy link

Being able to use AND as well as OR to combine different auth rules would make it much easier. We are currently facing the Issue, where we need to have the User (owner) to have access, as well as a group of Admins per Tenant.

IF we would be able to use AND we could just have a group "admins" and the tenant_ID in the groupfield. Then you could gran access if ( Owner || ( group: "admins" && groupfield matches)).

I feel like this feature should have been added a long time ago. Idk why this has been open since 2018 since it's such a good feature suggestion

@alexanderchan
Copy link

Given the complexity of this we’ve given up trying to manage using directives. In the end custom resolvers with the full power of a coding language and an auth a lambda is the better way to go for our use case when going off the beaten path rather than try to solve with a dsl. Probably not the solution people want to hear but perhaps will convince people to move off it before things go too far down a path.

@Alex-Github-Account
Copy link

Alex-Github-Account commented Nov 8, 2022

Why not at least add an global "option" to switch logic of combining @auth rules from "OR" to "AND" between "ownership" and "group" rules?
that would effectively mean that schema.graphql syntax NO needs to be changed, it would simply be interpreted with AND logic if switch is set somewhere in the AWS console.
Feature of supporting multi tenancy is critical for enterprise grade apps and entrie @auth is only applicable to student projects and tutorials if we cant split by tenants.

@8maxxam8
Copy link

Also interested in into this topic...

@josefaidt josefaidt mentioned this issue Feb 15, 2023
2 tasks
@mauriceburn
Copy link

mauriceburn commented May 16, 2023

Any news on this?
I was new to AWS and intimidated at first. But with Amplify it’s a piece of cake… until you try to do a multi tenant app :-((( PLEASE Implement this !!!

@przemekblasiak
Copy link

Dear AWS Team, this is critical in order to build any scalable SaaS product. We need it urgently, please let us know if that's coming or not anytime soon.

@redjonzaci
Copy link

Any update on this?

@redjonzaci
Copy link

Honestly, I don't see that here.

image

@dlamon1
Copy link

dlamon1 commented Sep 13, 2023 via email

@ronaldocpontes
Copy link

@mikeparisstuff @kaustavghosh06 @mdwt Is there anyone even looking at this? Is Amplify still actively supported by Amazon/AWS and if so, is multi-tenant app support a priority in Amplify's roadmap?

@maziarzamani
Copy link

My best guess is that this is not prioritised, hence I avoid using amplify for multi-tenant applications 😩

@chrisl777
Copy link

It would be awesome if there were a multi-tenant auth/api setup and schema out-of-the-box, i.e. a template that we could apply to spin up a multi-tenant application without so many gymnastics to make it work.

@ronaldocpontes
Copy link

@renebrandel Would #430 enable this use case for supporting Combining Owner/Groups rules for Multi-Tenant Apps?

@flobo79
Copy link

flobo79 commented Nov 7, 2023

Now how about we talk about alternative services which might kick AWS out out of bed? Firebase/Firestore doesnt work for me as I'm based in Germany / DSGVO compliance. What other services have you considered as alternatives for these requirements?

@trevorgildersleeve
Copy link

trevorgildersleeve commented Dec 4, 2023

Am I understanding this right that Gen2's introduction of composite identifiers is the answer to solving multi-tenancy?
https://docs.amplify.aws/gen2/build-a-backend/data/data-modeling/identifiers/

@chrisl777
Copy link

One thing to look at in Gen 2 (that also exists to some degree in Gen 1), is the ability to use a Lambda for custom data access.

https://docs.amplify.aws/gen2/build-a-backend/data/customize-authz/custom-data-access-patterns/

@chunyenHuang
Copy link

Am I understanding this right that Gen2's introduction of composite identifiers is the answer to solving multi-tenancy? https://docs.amplify.aws/gen2/build-a-backend/data/data-modeling/identifiers/

It seems to me it just represents the primary key and sort key.

One thing to look at in Gen 2 (that also exists to some degree in Gen 1), is the ability to use a Lambda for custom data access.

https://docs.amplify.aws/gen2/build-a-backend/data/customize-authz/custom-data-access-patterns/
This looks closer to the topic. Thanks for pointing out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests