Skip to content

RBAC setup using dynamic groups #2927

@blinkdaffer

Description

@blinkdaffer

Environment information

npx ampx info
System:
  OS: macOS 14.5
  CPU: (8) arm64 Apple M1 Pro
  Memory: 206.73 MB / 16.00 GB
  Shell: /bin/zsh
Binaries:
  Node: 20.9.0 - /usr/local/bin/node
  Yarn: 1.22.19 - /opt/homebrew/bin/yarn
  npm: 10.2.4 - /opt/homebrew/bin/npm
  pnpm: undefined - undefined
NPM Packages:
  @aws-amplify/auth-construct: 1.8.1
  @aws-amplify/backend: 1.16.1
  @aws-amplify/backend-ai: Not Found
  @aws-amplify/backend-auth: 1.7.1
  @aws-amplify/backend-cli: 1.8.0
  @aws-amplify/backend-data: 1.6.1
  @aws-amplify/backend-deployer: 2.1.3
  @aws-amplify/backend-function: 1.14.1
  @aws-amplify/backend-output-schemas: 1.7.0
  @aws-amplify/backend-output-storage: 1.3.1
  @aws-amplify/backend-secret: 1.4.0
  @aws-amplify/backend-storage: 1.4.1
  @aws-amplify/cli-core: 2.2.1
  @aws-amplify/client-config: 1.8.0
  @aws-amplify/data-construct: 1.16.3
  @aws-amplify/data-schema: 1.20.3
  @aws-amplify/deployed-backend-client: 1.8.0
  @aws-amplify/form-generator: 1.2.4
  @aws-amplify/model-generator: 1.2.0
  @aws-amplify/platform-core: 1.10.0
  @aws-amplify/plugin-types: 1.11.0
  @aws-amplify/sandbox: 2.1.2
  @aws-amplify/schema-generator: 1.4.0
  @aws-cdk/toolkit-lib: 1.1.1
  aws-amplify: 6.15.3
  aws-cdk-lib: 2.206.0
  typescript: 5.8.3
AWS environment variables:
  AWS_SDK_LOAD_CONFIG = 1
No CDK environment variables

Describe the bug

AWS Amplify Gen 2: Multi-Tenant RBAC Authorization Pattern

Database Schema

I have a multi-tenant SaaS application with this data structure:

Company Table

Company: a.model({
  id: a.id().required(),
  companyName: a.string(),
  companyBackground: a.string(),
  // ... other company fields
  
  // Relationships
  memberships: a.hasMany('CompanyMembership', 'companyId'),
})

CompanyMembership Table (Junction Table)

CompanyMembership: a.model({
  id: a.id().required(),
  userId: a.string().required(),  // Cognito user ID
  companyId: a.id().required(),   // Reference to Company
  role: a.enum(['owner', 'admin', 'editor', 'viewer']),
  status: a.enum(['active', 'inactive', 'pending']),
  
  // Relationships
  company: a.belongsTo('Company', 'companyId'),
})

JWT Token Structure

My PreTokenGeneration Lambda creates these custom claims:

{
  "sub": "user-cognito-id",
  "user_companies": "owner authenticated",          // Roles as string
  "company_access": "company-id-user-belongs-to",   // Single company ID
  "cognito:groups": ["company-id-owner", "owner", "authenticated"]
}

Current Problem

Users can query client.models.Company.list() and see ALL companies in the database, not just companies they belong to.

What I Want to Achieve

Multi-tenant RBAC where:

  1. Users can ONLY access companies they have active membership in
  2. Within each company, users have different permissions based on their role:
    • owner: Full CRUD access
    • admin: Full CRUD access
    • editor: Read + Update only
    • viewer: Read only

Current Authorization Attempt

Company: a.model({
  // ... fields
})
.authorization((allow) => [
  allow.ownerDefinedIn('company_access'),
  allow.groups(['owner', 'admin']).withClaimIn('user_companies').to(['read', 'create', 'update', 'delete']),
  allow.groups(['editor']).withClaimIn('user_companies').to(['read', 'update']),
  allow.groups(['viewer']).withClaimIn('user_companies').to(['read']),
])

Problem: The role-based rules grant access to ALL companies if user has the role, completely bypassing company scoping.

Questions

  1. What's the correct authorization pattern for multi-tenant RBAC in Amplify Gen 2?

  2. How should I structure my JWT claims to support both company scoping AND role-based permissions?

  3. Should I use only custom resolvers instead of model-level authorization for this use case?

  4. Is there a way to combine ownerDefinedIn() with groups().withClaimIn() to create AND logic instead of OR logic?

Looking for the recommended approach to implement secure multi-tenant authorization with AWS Amplify Gen 2.

Reproduction steps

NA

Metadata

Metadata

Assignees

No one assigned

    Labels

    authIssue pertaining to Amplify AuthdataIssue pertaining to Amplify Datafeature-requestNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions