Skip to content

Architecture

The system is split into two halves that only talk to each other through events. This split is the core idea of the AWS SaaS reference architectures.

Control plane Application plane
Who uses it The SaaS provider’s staff (system admins) Tenants’ users
What it does Registers and offboards tenants, stores tenant config, manages admin users The product itself: products, orders and tenant users
Where users live One Cognito user pool for system admins The pooled tenant user pool, or a silo tenant’s own pool
Stacks ControlPlaneStack serverless-saas-bootstrap, serverless-saas-tenant-template-*, serverless-saas-tenant-pipeline
Web app Admin console SaaS app
Architecture overview: web apps, control plane and application plane
  • The dashed arrow is the public tenant-config lookup the SaaS app makes before sign-in.
  • The control plane only reaches the application plane through EventBridge events.
  • The tenant template stack is the same CDK code deployed once for pooled tiers and once per Platinum tenant.

ControlPlaneStack is built with two constructs from the SaaS Builder Toolkit:

  • CognitoAuth: the system admin user pool. It creates the first admin, admin, from the email passed to deploy.sh, and a machine client for service-to-service calls.
  • ControlPlane: an HTTP API backed by Python Lambdas and DynamoDB, plus the EventBridge bus both planes share. See the Control plane API reference.

The control plane holds no tenant business data. It knows which tenants exist, how to reach each tenant’s resources and who the provider’s admins are.

  • Bootstrap stack (serverless-saas-bootstrap): resources that must exist before any tenant does. That’s the tenant mapping table (which stack serves which tenant), one API key per tier stored in SSM, the versioned source bucket, and the provisioning and deprovisioning jobs.
  • Tenant template stack: one CDK stack class deployed many times. serverless-saas-tenant-template-pooled serves every Basic, Standard and Premium tenant, and each Platinum tenant gets serverless-saas-tenant-template-<tenantId>. Each copy has a tenant user pool, a REST API with a Lambda authorizer and usage plans, and three services: products, orders and user management.
  • Tenant pipeline (serverless-saas-tenant-pipeline): rolls new code out to every tenant template stack. See Deployments.

serverless-saas-client is a separate CDK app in client/cdk. It hosts three sites with one StaticSite construct: a private S3 bucket behind CloudFront with Origin Access Control.

  • The admin console talks only to the control plane.
  • The SaaS app asks the control plane which user pool and API serve a tenant, then talks only to that tenant’s API.
  • These docs are an Astro Starlight site.

Each React app is built once. CDK writes a config.json next to it with the deployed URLs and ids, so the same build works in any account.

When a tenant user opens the Products page:

A GET /products request, end to end
  1. The SaaS app already knows the tenant’s apiGatewayUrl from GET /tenant-config/{name} and has an ID token from the tenant’s user pool.
  2. It calls GET {apiGatewayUrl}/products with Authorization: Bearer <ID token>. It never sends a tenant id.
  3. API Gateway calls the Lambda authorizer, or reuses its cached answer for that token for up to 30 seconds.
  4. The authorizer verifies the JWT, reads custom:tenantId, custom:userRole and custom:tenantTier, picks the tier’s API key and assumes a role with a session policy that only allows DynamoDB keys starting <tenantId>-.
  5. API Gateway checks the tier’s usage plan and returns 429 if the tenant is over its limit.
  6. The product Lambda reads the tenant context and scoped credentials from requestContext.authorizer, then queries the tenant’s 9 shards in parallel with those credentials.
  7. Logs, metrics and traces are tagged with the tenant id by the shared Lambda layer.

Tenant identity and Tenant isolation explain steps 4 and 6 in detail.