aws-amplify / aws-amplify/amplify-data

Dynamic, row-scoped authorization (e.g. “member of club”) does not compose with generated model queries + selection sets

Open
#648 4 comments 1 reaction 0 assignees View on GitHub
feature-request
Dominant language
TypeScript
Stars
18
Forks
23
Avg merge
26m
Merged PRs (30d)
1

Description

## Summary

I’m building an app with club-scoped data using [AWS Amplify](chatgpt://generic-entity?number=0) 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**

```ts
User
- id
- sub

Club
- id
- name

ClubMember // join table
- id
- clubId
- userId
- role
- status
```

**Desired behavior**

1. 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)

2. A user who is not a member of that club should receive Unauthorized.
3. 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:

```ts
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:

```ts
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:

```ts
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
1. Is there a recommended pattern for **dynamic, relationship-based authorization** that preserves generated model queries?
2. Are there plans to support **composable authorization hooks** (policy/middleware-style)?
3. Is the lack of `selectionSet `support in `client.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.

Contributor guide

Open the contributing guide

Research direction

Start with the minimal Club, User, and ClubMember scenario in the issue and reproduce both the working owner-based query and the failing club-membership case using generated model queries. Compare custom queries or resolvers with frontend selection sets, filters, pagination, and nested data; done would require a documented, composable authorization approach that preserves these capabilities.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, typescript
Domain
api, authorization
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.