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 |
The big picture
Section titled “The big picture”- 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.
Control plane
Section titled “Control plane”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 todeploy.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.
Application plane
Section titled “Application plane”- 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-pooledserves every Basic, Standard and Premium tenant, and each Platinum tenant getsserverless-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.
Web apps
Section titled “Web apps”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.
A request, end to end
Section titled “A request, end to end”When a tenant user opens the Products page:
- The SaaS app already knows the tenant’s
apiGatewayUrlfromGET /tenant-config/{name}and has an ID token from the tenant’s user pool. - It calls
GET {apiGatewayUrl}/productswithAuthorization: Bearer <ID token>. It never sends a tenant id. - API Gateway calls the Lambda authorizer, or reuses its cached answer for that token for up to 30 seconds.
- The authorizer verifies the JWT, reads
custom:tenantId,custom:userRoleandcustom:tenantTier, picks the tier’s API key and assumes a role with a session policy that only allows DynamoDB keys starting<tenantId>-. - API Gateway checks the tier’s usage plan and returns
429if the tenant is over its limit. - 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. - 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.