Tenant identity
Every request carries its tenant context inside a signed JWT. Nothing in the request body or URL is trusted to say who the tenant is.
Claims
Section titled “Claims”Tenant users live in a Cognito user pool with three custom attributes. Cognito copies them into the ID token:
| Claim | Example | Used for |
|---|---|---|
custom:tenantId |
6e02dc17-5c4c-… |
Which tenant’s data the caller can reach |
custom:userRole |
TenantAdmin or TenantUser |
What the caller may do |
custom:tenantTier |
BASIC |
Which API key and usage plan apply |
cognito:groups |
["6e02dc17-5c4c-…"] |
Each tenant also has a Cognito group, used to list and check its users |
Decoded claims from a pooled tenant admin:
{ "cognito:groups": ["tenant-a"], "custom:tenantTier": "BASIC", "cognito:username": "alice", "custom:tenantId": "tenant-a", "custom:userRole": "TenantAdmin" }The SaaS app’s dashboard shows these claims next to what the authorizer derived from them.
Users can’t edit their own claims
Section titled “Users can’t edit their own claims”By default a Cognito app client lets a signed-in user change any of their own attributes with their access token, without going through your API. If custom:tenantId were writable, any user could move themselves into another tenant.
So the app client’s writeAttributes allows only email. Tenant attributes are only ever set by admin APIs (AdminCreateUser, AdminUpdateUserAttributes), which only the backend can call:
aws cognito-idp update-user-attributes --access-token "$ACCESS" \ --user-attributes Name=custom:tenantId,Value=tenant-b# NotAuthorizedException: A client attempted to write unauthorized attributeWhere users come from
Section titled “Where users come from”| Who | Created by | Gets |
|---|---|---|
| The tenant’s first admin | The provisioning job at onboarding | TenantAdmin, the tenant’s id and tier, a temporary password by email |
| More tenant users | A TenantAdmin in the SaaS app (POST /users) |
The role chosen. Tenant id and tier are inherited from the creating admin’s token, whatever the request body says |
| Role | Can |
|---|---|
TenantAdmin |
Everything in the tenant: products, orders and user management |
TenantUser |
Products and orders, and read the user list |
SystemAdmin |
Exists only in the control plane pool. The tenant API’s authorizer denies it, so system admins can’t read tenant data |
Role rules live in one place, server/src/layers/auth_manager.py. See step 07 for every user management rule and how it’s enforced.