Amazon Cognito User Pools provide a secure user directory that scales to hundreds of millions of users. As a fully managed service, User Pools are easy to set up without any worries about standing up server infrastructure. Terraform brings repeatable, versioned configuration to Cognito, with two dominant community module patterns and native resource definitions that expose the full Cognito API.
Introduction
Terraform for Cognito sits at the intersection of identity security, OAuth configuration, and immutable infrastructure constraints. Cognito settings are largely immutable after creation. Terraform will try to destroy and recreate the pool, which means losing all users. Plan these carefully. That makes getting the initial setup right particularly important, and it's why Terraform is so valuable here.
This article covers module-based workflows using lgallard/terraform-aws-cognito-user-pool and mineiros-io/terraform-aws-cognito-user-pool, plus native resource patterns for custom attributes, log delivery, resource servers, Lambda triggers, and hosted UI configuration.
Module Approaches for Cognito User Pools
lgallard Cognito User Pool Module
The lgallard module creates Amazon Cognito User Pools, configure its attributes and resources such as app clients, domain, resource servers. You can use this module to create a Cognito User Pool using the default values or use the detailed definition to set every aspect of the Cognito User Pool.
Examples include the simple example using default values, the simpleextended version which adds app clients, domain, resource servers resources, the complete version with a detailed example, or the withbranding example that demonstrates managed login branding capabilities.
Simple default usage:
hcl
module "aws_cognito_user_pool_simple" {
source = "lgallard/cognito-user-pool/aws"
user_pool_name = "mypool"
# Recommended: Enable schema ignore changes for new deployments
# This prevents perpetual diffs if you plan to use custom schemas
ignore_schema_changes = true
tags = {
Owner = "infra"
Environment = "production"
Terraform = true
}
}
Conditional creation for older Terraform forms is supported:
hcl
module "aws_cognito_user_pool_conditional_creation" {
source = "lgallard/cognito-user-pool/aws"
user_pool_name = "conditional_user_pool"
enabled = false
ignore_schema_changes = true
tags = {
Owner = "infra"
Environment = "production"
Terraform = true
}
}
For Terraform 0.14 and later you can use count inside module blocks, or use the input variable enabled as described above.
Log Delivery Configuration
You can configure Cognito user pool log delivery for notification errors and threat-protection user activity logs. Notification logs use userNotification with ERROR and CloudWatch Logs.
```hcl
resource "awscloudwatchloggroup" "cognitousernotificationerrors" {
name = "/aws/vendedlogs/cognito/mypool/user-notification-errors"
retentionindays = 30
}
module "awscognitouserpoolwithlogdelivery" {
source = "lgallard/cognito-user-pool/aws"
userpoolname = "mypool"
ignoreschemachanges = true
logdeliveryconfiguration = {
logconfigurations = [
{
eventsource = "userNotification"
loglevel = "ERROR"
cloudwatchlogsconfiguration = {
loggrouparn = awscloudwatchloggroup.cognitousernotificationerrors.arn
}
}
]
}
}
```
See examples/log_delivery for a complete example.
Mineiros Cognito User Pool Module
This module supports Terraform v1.x, v0.15, v0.14, v0.13 as well as v0.12.20 and above and is compatible with the Terraform AWS provider v3.50 and above.
Module features include Getting Started, Module Argument Reference, Module Outputs, External Documentation, Module Versioning, About Mineiros, Reporting Issues, Contributing, Makefile Targets, License.
In contrast to the plain cognitouserpool resource this module has a more secure level of default settings. While all settings can be customized as needed, best practices are pre-configured.
Default Security Settings:
Per default, only administrators are allowed to create user profiles by setting allowadmincreateuseronly to true. This module comes with a strong default password policy.
Standard Cognito Features:
- Create a Cognito User Pool with pre-configured best practices
- Create Cognito User Pool Clients
- Create a Cognito User Pool Domain
- Create Cognito User Pool Resource Servers as associated scopes
Features not yet implemented:
cognitousergroup
Most basic usage just setting required arguments:
hcl
module "terraform-aws-cognito-user-pool" {
source = "mineiros-io/cognito-user-pool/aws"
version = "~> 0.9.0"
name = "application-userpool"
}
Advanced usage as found in examples/complete/main.tf setting all required and optional arguments to their default values.
Module argument reference includes module_enabled:
module_enabled: (Optional bool) Specifies whether resources in the module will be created. Default is true.
Module Comparison
| Capability | lgallard Module | mineiros-io Module |
|---|---|---|
| Terraform versions | Supports modern usage with enabled flag | Terraform v1.x, v0.15, v0.14, v0.13, v0.12.20+ |
| AWS Provider | Implicit via module | Compatible with Terraform AWS provider v3.50 and above |
| Default security posture | Configurable via ignoreschemachanges | Pre-configured best practices, allowadmincreateuseronly true by default |
| Log delivery | logdeliveryconfiguration with event_source userNotification ERROR | Not detailed in reference |
| Conditional creation | enabled = false | module_enabled optional bool default true |
| Branding | with_branding example | Not detailed in reference |
| Resource servers | simple_extended example | Create Cognito User Pool Resource Servers as associated scopes |
Native Resource Configuration
Using the AWS provider directly gives full control over Cognito's immutable settings. Cognito with Terraform is a great combination because so many Cognito settings are immutable after creation.
Basic User Pool with Email Sign-In
```hcl
resource "awscognitouserpool" "main" {
name = "myapp-users"
usernameattributes = ["email"]
autoverifiedattributes = ["email"]
passwordpolicy {
minimumlength = 12
requirelowercase = true
requireuppercase = true
requirenumbers = true
requiresymbols = false
temporarypasswordvalidity_days = 7
}
accountrecoverysetting {
recoverymechanism {
name = "verifiedemail"
priority = 1
}
}
schema {
name = "email"
attributedatatype = "String"
required = true
mutable = true
stringattributeconstraints {
minlength = 5
maxlength = 256
}
}
schema {
name = "name"
attributedatatype = "String"
required = true
mutable = true
stringattributeconstraints {
minlength = 1
maxlength = 256
}
}
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}
```
Important: username_attributes and schema settings with required = true cannot be changed after the user pool is created.
Custom Attributes
You can add custom attributes for application-specific data:
```hcl
resource "awscognitouserpool" "withcustomattrs" {
name = "myapp-users-v2"
usernameattributes = ["email"]
schema {
name = "company"
attributedatatype = "String"
mutable = true
stringattributeconstraints {
minlength = 1
maxlength = 256
}
}
schema {
name = "role"
attributedatatype = "String"
mutable = true
stringattributeconstraints {
minlength = 1
maxlength = 50
}
}
schema {
name = "accountid"
attributedatatype = "Number"
mutable = false
numberattributeconstraints {
minvalue = 1
max_value = 999999999
}
}
}
```
Custom attributes are accessed as custom:company, custom:role, etc. in tokens and API calls. Also note: custom attributes can't be removed or changed once created. You can only add new ones.
Resource Server and Custom OAuth Scopes
If your API needs custom OAuth scopes, define a resource server:
```hcl
resource "awscognitoresourceserver" "api" {
identifier = "api"
name = "My API"
userpoolid = awscognitouserpool.main.id
scope {
scopename = "read"
scopedescription = "Read access"
}
scope {
scopename = "write"
scopedescription = "Write access"
}
}
```
After creating the resource server, use scopes like api/read and api/write in your app clients.
Lambda Triggers
Common use cases for triggers:
- Pre sign-up: Block certain email domains, auto-verify emails
- Post confirmation: Create a user record in your database, send a welcome email
- Pre token generation: Add custom claims based on user attributes
- Custom message: Localize or brand verification emails
Outputs export the values your application needs:
```hcl
output "userpoolid" {
value = awscognitouser_pool.main.id
}
output "userpoolarn" {
value = awscognitouser_pool.main.arn
}
output "clientid" {
value = awscognitouserpool_client.web.id
}
output "hosteduidomain" {
value = "https://${awscognitouserpooldomain.main.domain}.auth.${var.aws_region}.amazoncognito.com"
}
```
Email Configuration
By default, Cognito sends emails using a shared address with strict rate limits.
Immutable Planning and Safe Upgrades
In this guide, we'll build a Cognito user pool from scratch, add app clients, configure the hosted UI, and wire up Lambda triggers for custom logic.
Cognito has a lot of configuration options, and many of them can't be changed after creation. That makes getting the initial setup right particularly important, and it's why Terraform is so valuable here.
Key immutable considerations:
- username_attributes cannot be changed
- schema attributes with required = true cannot be changed
- Custom attributes cannot be removed or changed once created
- Password policy changes are limited after creation
Use ignoreschemachanges = true to prevent perpetual diffs if you plan to use custom schemas.
Module Outputs and Example Patterns
The lgallard module supports app clients, domain, resource servers resources in the simple_extended version. The complete version with a detailed example shows full attribute configuration.
The mineiros module provides module argument reference and module outputs with pre-configured best practices.
Practical Terraform Patterns
TL;DR: This is a simple article on how to use Terraform to setup and maintain an AWS Cognito user pool. If you are in a hurry and you already know Terraform the example below should be enough for you.
A simple article on how to use Terraform to setup and maintain an AWS Cognito user pool.
For conditional resources in earlier form versions such as 0.11, 0.12 and 0.13 you can set the input variable enabled to false.
| Pattern | Use Case |
|---|---|
| ignoreschemachanges = true | Prevent perpetual diffs for custom schemas |
| enabled = false | Skip creation in older Terraform versions |
| module_enabled | Toggle creation in mineiros module |
| logdeliveryconfiguration | Ship userNotification ERROR to CloudWatch |
| allowadmincreateuseronly true | Default secure setting in mineiros module |
Conclusion
Terraform AWS Cognito User Pool management requires careful handling of immutable settings and a clear choice between module convenience and native resource control. The lgallard module offers flexible examples for default values, extended resources, log delivery, and branding with an ignoreschemachanges safeguard for custom schemas. The mineiros-io module brings version support for Terraform v1.x down to v0.12.20 and AWS provider v3.50+, with pre-configured best practices such as allowadmincreateuseronly true and a strong default password policy.
Native resource definitions remain essential for custom attributes, resource servers with scopes like api/read and api/write, Lambda triggers for pre sign-up, post confirmation, pre token generation, and custom message workflows, and precise password policy and account recovery settings. Outputs such as userpoolid, userpoolarn, clientid, and hostedui_domain complete the integration.
Plan immutable fields at creation time: username_attributes, required schema, custom attributes, and password policy constraints. Use module toggles and log delivery for operational safety. This combination delivers repeatable, secure Cognito deployments at scale.