AWS Cognito + Verified Permissions for SaaS Authorization
A deep dive into building SaaS authorization with AWS Cognito and Verified Permissions. Covers Cedar policy language, multi-tenant patterns, JWT token flow, cost analysis, and common mistakes with TypeScript examples.
Abstract
AWS Cognito handles authentication. AWS Verified Permissions (AVP) handles authorization. Together, they form AWS's native SaaS authorization stack. This post walks through how these two services integrate, how Cedar policies enforce tenant isolation, and how to structure multi-tenant authorization for B2B SaaS products. It includes TypeScript integration code, Cedar policy examples, a cost breakdown, and a comparison with the Microsoft Entra ID approach.
Note: This is Part 2 of the External Authorization Systems series. Part 1 covers the broader authorization platform landscape and decision framework.
Cognito's Role in Authorization
Cognito is an authentication service, not an authorization engine. But authentication and authorization are tightly coupled in SaaS systems. Cognito's output -- JWT tokens with claims -- becomes the input for authorization decisions.
JWT Tokens and Claims
After a user authenticates, Cognito issues three tokens:
- ID Token: Contains user identity claims (email, name, custom attributes). Used for user profile information.
- Access Token: Contains scopes and authorization-related claims. Used for API authorization.
- Refresh Token: Used to obtain new ID and access tokens without re-authentication.
For authorization, the access token matters most. It carries the claims that AVP Cedar policies evaluate.
Custom Claims and Tenant Context
Cognito supports custom attributes on user profiles (prefixed with custom:). For SaaS, the critical custom attributes are tenant identifiers and organizational roles:
custom:tenantId-- The tenant this user belongs tocustom:orgRole-- The user's role within the tenant (admin, editor, viewer)custom:tenantTier-- The tenant's subscription tier (free, pro, enterprise)
These attributes are set during user registration or by an admin API. They are immutable to the user (read-only on the app client) and included in JWT tokens after authentication.
Pre Token Generation Trigger
The Pre Token Generation Lambda trigger is the key integration point between Cognito and AVP. It runs before Cognito issues the access token, allowing you to enrich the token with additional claims that Cedar policies consume.
Tip: Keep custom claims minimal. Every additional claim increases token size. Cognito access tokens have a size limit, and bloated tokens add latency to every API call. Only include claims that Cedar policies actively evaluate.
User Pool vs. Identity Pool
Cognito has two components that serve different purposes:
- User Pool: Manages user authentication (sign-up, sign-in, MFA, password recovery). Issues JWT tokens. This is where tenant context lives.
- Identity Pool: Maps authenticated users to temporary AWS credentials (IAM roles). Used for direct AWS resource access (S3, DynamoDB).
For SaaS authorization with AVP, you primarily use the User Pool. The Identity Pool is relevant only if your application needs direct AWS resource access from the client side.
AWS Verified Permissions Deep Dive
AVP is a managed authorization service that uses the Cedar policy language. It evaluates authorization requests against Cedar policies and returns allow or deny decisions.
Cedar Policy Language
Cedar is declarative, statically analyzable, and formally verified. Policies express who (principal) can do what (action) to which resource, under what conditions.
A basic Cedar policy:
Cedar supports two policy types:
- permit: Grants access when conditions match
- forbid: Denies access even if a permit policy matches (forbid always wins)
This "deny overrides permit" model makes it straightforward to add safety constraints:
Schema Definition
AVP uses a Cedar schema to define your entity types, their attributes, and valid action-entity combinations. The schema is optional but strongly recommended for production. It enables policy validation at creation time, catching typos and structural errors before they reach production.
Policy Store Configuration
A policy store is the container for your Cedar schema, policies, and identity source configuration. The key architectural decision is whether to use one policy store per tenant or a shared policy store for all tenants.
SaaS Multi-Tenant Patterns
Multi-tenancy is the core challenge in SaaS authorization. The question is how to isolate tenants from each other while keeping the authorization infrastructure manageable.
Pool Model vs. Silo Model
Pool model: Single Cognito User Pool with custom:tenantId attribute. Single AVP policy store with tenant-scoped Cedar policies.
- Simpler to manage and deploy
- Lower cost (one User Pool, one policy store)
- Tenant isolation enforced at the Cedar policy level
- Works for most B2B SaaS products
- Risk: a policy bug could leak data across tenants
Silo model: Separate User Pool and policy store per tenant.
- Stronger isolation boundary (infrastructure-level separation)
- Required for compliance-heavy industries (healthcare, finance, government)
- Higher operational overhead and cost
- Each tenant can have customized policies
- More complex deployment and management
Tip: Start with the pool model. It handles most cases well. Move to silo only when compliance or contractual requirements demand infrastructure-level tenant isolation.
Cedar Policies for Multi-Tenancy
In the pool model, tenant isolation is entirely policy-driven. Every resource access policy must include a tenant check:
For tier-based feature gating, Cedar policies can check the tenant tier:
Cross-Tenant Access
Some SaaS products need controlled cross-tenant access -- for example, a consultant accessing multiple client tenants. This requires explicit cross-tenant policies:
Warning: Cross-tenant policies are powerful and risky. They bypass the standard tenant isolation boundary. Review them carefully and test with dedicated integration tests.
Integration Architecture
The following diagram shows how Cognito tokens flow through the system to reach AVP for authorization decisions.
The flow works in two tiers:
- API Gateway + Cognito Authorizer (coarse-grained): Validates the JWT token, checks that the user is authenticated and the token is not expired. Rejects unauthenticated requests before they reach Lambda.
- Lambda + AVP (fine-grained): Extracts tenant context from the validated token, calls AVP with the principal, action, and resource. AVP evaluates Cedar policies and returns allow or deny.
JWT Extraction and Tenant Context
IsAuthorized and IsAuthorizedWithToken
AVP provides two authorization APIs:
- IsAuthorized: You construct the principal entity manually. Useful when the caller is a backend service, not a user with a JWT.
- IsAuthorizedWithToken: You pass the raw Cognito JWT token. AVP validates the token and extracts the principal automatically. This is simpler and more secure for user-facing requests.
BatchIsAuthorized for UI Rendering
When rendering a UI with multiple resources (a document list, a dashboard with permission-gated features), making individual authorization calls is expensive. BatchIsAuthorizedCommand sends up to 30 checks in a single API call.
The constraint: either the principal or the resource must be identical across all requests in the batch. For UI rendering, you typically fix the principal (the current user) and vary the action/resource.
API Middleware Pattern
A reusable middleware that connects Cognito authentication with AVP authorization:
Entra ID Alternative
For teams in the Microsoft ecosystem, the equivalent pattern uses Microsoft Entra External ID for authentication and a separate authorization layer for fine-grained decisions.
Key Differences
The critical gap in the Microsoft stack is the absence of a managed fine-grained authorization service. Entra handles authentication and coarse-grained authorization (app roles, group memberships, Conditional Access), but for resource-level decisions like "can user X edit document Y in tenant Z," you need an external PDP.
In practice, Microsoft-stack teams pair Entra with Cerbos or OPA for fine-grained decisions. The Entra token provides identity context (user ID, roles, groups, tenant ID), and the external PDP evaluates policies using that context plus resource attributes.
AWS has a clear advantage here: Cognito + AVP is a fully integrated, managed authorization stack. In the Microsoft ecosystem, you assemble it from multiple parts.
Cost Analysis
AVP Pricing
Amazon Verified Permissions bills single-authorization APIs, batch-authorization APIs, and policy management APIs on different rate cards. There is no single “flat” rate for every API. See Amazon Verified Permissions Pricing.
Single authorization (IsAuthorized, IsAuthorizedWithToken): each API call is one metered authorization request at 5 per million).
Batch authorization (BatchIsAuthorized, BatchIsAuthorizedWithToken): each batch API call is metered as one request, regardless of how many checks you include in that call (up to the service limit). Batch calls use tiered per-call pricing (for example, the first 40 million batch calls per month at $0.00015 per batch call on the published US rate card). That is not the same as paying the single-auth rate for every inner check.
The table below applies only when every fine-grained decision maps to one IsAuthorized or IsAuthorizedWithToken call.
Note: If you rely heavily on batch APIs, model cost using the batch tiers on the pricing page. Policy management calls (create, update, list policies, etc.) are billed separately.
Cognito Pricing
Cognito pricing depends on the tier:
Note: Accounts with active Cognito user pools before November 2024 are eligible for a higher free tier of 50,000 MAUs.
Total SaaS Auth Cost Example
For a B2B SaaS product with 5,000 MAUs making an average of 200 API calls per day:
- Cognito (Lite): Free (under 10,000 MAU threshold)
- AVP: 5,000 users x 200 calls x 30 days = 30 million single authorization API calls/month = $150/month (assuming each call is
IsAuthorizedorIsAuthorizedWithTokenat the single-auth rate) - Total: ~$150/month for authentication + fine-grained authorization
If you satisfy the same checks with batch APIs instead, AWS meters batch API calls at the batch rate card, not one billed unit per inner authorization check.
For comparison, a self-hosted solution (SpiceDB on Kubernetes + PostgreSQL) would cost $500-2,000/month in infrastructure alone, plus engineering time for operations.
Tip: Not every API call needs an AVP check. Use the two-tier pattern: API Gateway handles coarse-grained checks (is the user authenticated? does the token have the right scope?). Only route fine-grained decisions to AVP. This can reduce AVP request volume by 60-80%.
Common Mistakes
Over-Scoping Policy Stores
Creating a separate policy store per microservice leads to policy fragmentation. Authorization decisions that span multiple services become impossible within a single policy store. In most cases, a single policy store per environment (dev, staging, production) is sufficient. Use Cedar namespacing (entity type prefixes) to organize policies logically.
Missing Tenant Isolation in Policies
The most dangerous mistake in multi-tenant SaaS: writing a permit policy without a tenant check.
This policy allows any authenticated user to view any document, regardless of tenant. The fix is straightforward but easy to forget:
Warning: Write a Cedar policy test that explicitly verifies cross-tenant access is denied. Run it in CI/CD. This is your safety net against accidental tenant data leakage.
Token Bloat
Adding too many claims to Cognito tokens through the Pre Token Generation trigger increases token size. Large tokens add latency to every API call and can exceed size limits. Keep claims to what Cedar policies actually evaluate. If you need additional context for authorization, pass it as resource attributes in the AVP request rather than token claims.
Not Using IsAuthorizedWithToken
Manually extracting JWT claims and constructing principal entities is error-prone. IsAuthorizedWithToken lets AVP handle token validation and claim extraction. It is more secure (AVP verifies the token signature) and less code to maintain.
Ignoring Batch Authorization for UI
Making individual IsAuthorized calls in a loop for UI rendering creates latency problems and increases single-auth billing units. When you need many checks for the same principal, BatchIsAuthorizedCommand packs up to 30 checks into one batch API call, which is metered as one batch request (with its own tiered price per batch call). Compare single-auth versus batch totals on the pricing page for your traffic shape.
Skipping Schema Validation
Deploying Cedar policies without a schema means typos in attribute names silently fail. A policy checking principal.tenantid (lowercase 'd') instead of principal.tenantId will never match, and no error is reported. Enable schema validation to catch these issues at policy creation time.
Conclusion
Cognito + AVP provides a managed, integrated authorization stack for SaaS products on AWS. The combination handles authentication, tenant context enrichment, and fine-grained policy evaluation without self-hosted infrastructure.
The key takeaways:
- Use the Pre Token Generation trigger to enrich access tokens with tenant context that Cedar policies consume.
- Start with the pool model (single User Pool, single policy store) and move to silo only when compliance demands it.
- Every Cedar policy must include a tenant check in multi-tenant applications. Test cross-tenant isolation in CI/CD.
- Use IsAuthorizedWithToken for user-facing requests. Use IsAuthorized for service-to-service calls.
- Use the two-tier pattern (API Gateway for coarse checks, AVP for fine-grained) to control costs and latency.
- Use BatchIsAuthorized for UI rendering to reduce API calls and latency.
The next post in this series covers SpiceDB vs Auth0 FGA for relationship-based access control, where the authorization model is fundamentally different from the attribute-based approach covered here.
References
- Amazon Verified Permissions Documentation - Official guide to AVP concepts, schema definition, and policy evaluation
- Cedar Policy Language Documentation - Official Cedar language reference, grammar specification, and policy writing guides
- Amazon Verified Permissions Policy Store Schema - Schema definition for entity types, attributes, and action-entity mappings
- Working with Amazon Cognito Identity Sources - Configuring Cognito as an identity source for AVP
- IsAuthorizedWithToken API Reference - API documentation for token-based authorization requests
- Cognito Multi-Tenant Application Best Practices - AWS guidance on multi-tenancy patterns with Cognito
- Custom-Attribute Multi-Tenancy Best Practices - Using custom attributes for tenant isolation in Cognito
- Amazon Verified Permissions Pricing - Single versus batch authorization and policy management rates (single
IsAuthorized/IsAuthorizedWithTokencalls are $5 per million in the published US rate card) - Amazon Cognito Pricing - Cognito pricing tiers (Lite, Essentials, Plus)
- AWS Prescriptive Guidance - Multi-tenant SaaS Authorization - PDP deployment models for SaaS applications
- Microsoft Entra External ID Overview - Microsoft's customer-facing identity platform replacing Azure AD B2C
External Authorization Systems
A comprehensive guide to external authorization platforms for distributed systems. Covers platform selection, policy language comparison, cloud-native authorization with AWS, and relationship-based access control with SpiceDB and Auth0 FGA.