Skip to content

SaaS app

The SaaS app is the product that tenants’ users work in. One deployed app serves every tenant, pooled or silo: it finds out which user pool and API to use when the user types their tenant name.

Live: saas.haibui.click

The SaaS App start screen asking for the organization’s tenant name, with a Continue button.

The user types their organization’s tenant name, for example acme. The app calls the control plane’s public GET /tenant-config/acme and gets back:

{ "userPoolId": "us-east-1_a1M1hAn8y", "appClientId": "q33c…", "apiGatewayUrl": "https://0xlsgzuyxh.execute-api.us-east-1.amazonaws.com/prod/" }

For a Platinum tenant those are the silo stack’s own pool and API. The app configures Amplify for that pool and caches the config in localStorage, so the lookup only happens when a tenant is chosen. An unknown name shows Unknown tenant "<name>".

How one SaaS app serves every tenant: look up the tenant's config, sign in to its user pool, call its API

Then the user signs in with their username and password. A user created by onboarding or by a tenant admin has a temporary password and is asked to choose a new one. Use a different tenant goes back to the tenant screen.

The acme dashboard showing 4 products, 2 orders and tier BASIC, and a table comparing the token claims with what the authorizer returned for tenant id, role, tier and API key.

The tiles count the tenant’s products and orders and show its tier. The Your tenant context table is a window into the isolation model:

  • Token claim: what’s in the user’s ID token (custom:tenantId, custom:userRole, custom:tenantTier).
  • Authorizer: what the Lambda authorizer derived from that token, from GET /whoami, including the API key of the tier’s usage plan. The key is never in the token or the browser’s requests.

If the two columns ever disagree, something is wrong. See Tenant identity.

The Products page with a form to add a product by SKU, name, price and category, and a table of acme’s products: Anvil XL, Bird Seed, Portable Hole and Rocket Skates, each with Edit and Delete.

Add a product with the form, Edit a row to load it into the form, or Delete it. Every user of the tenant can manage products. The key behind each row, such as 6e02dc17-…-5:40a0b8a1-…, contains the tenant’s shard. That’s how the API checks ownership before touching DynamoDB.

The Orders page with a New order form listing each product with a quantity field, and a table of orders Desert expedition and Canyon trap kit with their items as quantity and product id, and totals.

Name an order, set quantities for the products you want and select Place order. Each line stores the product’s id, price and quantity at the time of the order, so later price changes don’t rewrite old orders. The table lists each order’s items as quantity × product id, and the totals are computed in the browser from the stored prices.

The Users page for acme with an invite form for username, email and role, and a table of the tenant’s users with role, status, enabled and Disable and Delete actions.

Every user can see the tenant’s user list. Only a TenantAdmin sees the invite form and the Disable, Enable and Delete actions:

  • Invite user creates a Cognito user in the tenant’s group, with a temporary password sent by email. The new user’s tenant id and tier are inherited from the admin’s token.
  • Disable blocks sign-in without deleting the user. Enable reverses it.
  • Delete removes the user.

Each tier has a request rate and daily quota (see Tiers). Over the limit, the API returns 429 and the app shows Too many requests for your tier (usage plan limit).