-
Notifications
You must be signed in to change notification settings - Fork 17
Description
Summary
I’m building an app with club-scoped data using AWS Amplify Gen 2 Data.
A common access rule is:
“A user may read data for a given clubId only if they are a member of that club.”
Today, this rule cannot be expressed in model authorization, and solving it via custom queries or resolvers breaks important features like frontend-controlled selection sets, filters, and reuse of generated model APIs.
⸻
Minimal example scenario
Models
User
- id
- sub
Club
- id
- name
ClubMember // join table
- id
- clubId
- userId
- role
- statusDesired behavior
-
A user who is a member of a club should be able to:
- list all members of that club
- fetch nested data (e.g. member → user display name)
-
A user who is not a member of that club should receive Unauthorized.
-
The frontend should be able to:
- use generated model queries (or equivalent)
- control selection sets for nested objects
- avoid N+1 queries
⸻
What works today
Case 1: “My memberships”
Using allow.ownerDefinedIn(...) works well for:
listClubMembersByOwner(...)This supports:
- generated model queries
- frontend selectionSet
- no custom resolvers
⸻
What does NOT work
Case 2: “List members of a club I belong to”
This rule is dynamic:
allow if a row exists in ClubMember for (clubId, user)
There is currently no way to express this as:
allow.if( isMemberOfClub(ctx.identity, ctx.args.clubId) )⸻
Attempted solutions & issues
1. Custom query (Lambda or AppSync JS resolver)
✔ Can enforce membership
❌ Cannot use generated client.models.* queries
❌ Cannot pass frontend selectionSet via client.queries.*
❌ Requires duplicating shapes (custom types, mapping, nested selection logic)
2. Lambda authorizer
✔ Can enforce access globally
❌ Replaces (rather than composes with) allow.owner, allow.groups
❌ Forces re-implementation of existing auth semantics
3. Frontend N+1 queries
❌ Inefficient
❌ Complex
❌ Defeats GraphQL’s purpose
⸻
Core problem
There is no composable way to say:
“Run the existing model authorization AND also check this custom condition.”
What’s missing is a concept similar to middleware / policy hooks that:
- run before resolvers
- have access to args (clubId)
- can perform a lookup
- do not replace existing allow.owner, allow.groups, etc.
⸻
Desired capability (conceptual)
Something like:
allow.custom((ctx) => {
return isMemberOfClub(ctx.identity.sub, ctx.args.clubId)
})Where:
- This check runs in addition to model auth
- Generated resolvers remain usable
- Frontend keeps selection sets, filters, pagination
- No need to re-implement existing queries
⸻
Why this matters
This pattern appears in many real apps:
- teams / organizations
- projects
- workspaces
- classrooms
- clubs
Currently, developers must choose between:
- strong authorization or
- good GraphQL ergonomics
⸻
Questions for the Amplify team
- Is there a recommended pattern for dynamic, relationship-based authorization that preserves generated model queries?
- Are there plans to support composable authorization hooks (policy/middleware-style)?
- Is the lack of
selectionSetsupport inclient.queries.*intentional or a current limitation?
⸻
Closing
I really like Amplify Gen 2’s direction — especially the data modeling and generated APIs.
This gap is the one place where I consistently feel forced to “drop down a level” and lose the benefits of the system.
Happy to provide a runnable repro if useful.