Create AWS Cognito User Pools

The aws:cognito/userPool:UserPool resource, part of the Pulumi AWS provider, defines a Cognito User Pool that manages user registration, authentication, and account recovery. This guide focuses on three capabilities: basic pool creation, MFA configuration, and account recovery settings.

User pools require IAM roles for SMS delivery and may integrate with SES for email, Lambda for custom workflows, and other AWS services. The examples are intentionally small. Combine them with your own password policies, email configuration, and custom attributes.

Create a minimal user pool with a name

Most deployments start by creating a user pool with a unique name, establishing the container for authentication.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const pool = new aws.cognito.UserPool("pool", {name: "mypool"});
import pulumi
import pulumi_aws as aws

pool = aws.cognito.UserPool("pool", name="mypool")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cognito"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cognito.NewUserPool(ctx, "pool", &cognito.UserPoolArgs{
			Name: pulumi.String("mypool"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var pool = new Aws.Cognito.UserPool("pool", new()
    {
        Name = "mypool",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cognito.UserPool;
import com.pulumi.aws.cognito.UserPoolArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var pool = new UserPool("pool", UserPoolArgs.builder()
            .name("mypool")
            .build());

    }
}
resources:
  pool:
    type: aws:cognito:UserPool
    properties:
      name: mypool

The name property sets a unique identifier for the pool. Without additional configuration, the pool uses Cognito defaults: MFA is OFF, password policy follows AWS standards, and email delivery uses Cognito’s built-in service.

Enable SMS and authenticator app MFA

Applications requiring stronger authentication enable MFA to require a second factor beyond username and password.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.cognito.UserPool("example", {
    mfaConfiguration: "ON",
    smsAuthenticationMessage: "Your code is {####}",
    smsConfiguration: {
        externalId: "example",
        snsCallerArn: exampleAwsIamRole.arn,
        snsRegion: "us-east-1",
    },
    softwareTokenMfaConfiguration: {
        enabled: true,
    },
});
import pulumi
import pulumi_aws as aws

example = aws.cognito.UserPool("example",
    mfa_configuration="ON",
    sms_authentication_message="Your code is {####}",
    sms_configuration={
        "external_id": "example",
        "sns_caller_arn": example_aws_iam_role["arn"],
        "sns_region": "us-east-1",
    },
    software_token_mfa_configuration={
        "enabled": True,
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cognito"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cognito.NewUserPool(ctx, "example", &cognito.UserPoolArgs{
			MfaConfiguration:         pulumi.String("ON"),
			SmsAuthenticationMessage: pulumi.String("Your code is {####}"),
			SmsConfiguration: &cognito.UserPoolSmsConfigurationArgs{
				ExternalId:   pulumi.String("example"),
				SnsCallerArn: pulumi.Any(exampleAwsIamRole.Arn),
				SnsRegion:    pulumi.String("us-east-1"),
			},
			SoftwareTokenMfaConfiguration: &cognito.UserPoolSoftwareTokenMfaConfigurationArgs{
				Enabled: pulumi.Bool(true),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.Cognito.UserPool("example", new()
    {
        MfaConfiguration = "ON",
        SmsAuthenticationMessage = "Your code is {####}",
        SmsConfiguration = new Aws.Cognito.Inputs.UserPoolSmsConfigurationArgs
        {
            ExternalId = "example",
            SnsCallerArn = exampleAwsIamRole.Arn,
            SnsRegion = "us-east-1",
        },
        SoftwareTokenMfaConfiguration = new Aws.Cognito.Inputs.UserPoolSoftwareTokenMfaConfigurationArgs
        {
            Enabled = true,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cognito.UserPool;
import com.pulumi.aws.cognito.UserPoolArgs;
import com.pulumi.aws.cognito.inputs.UserPoolSmsConfigurationArgs;
import com.pulumi.aws.cognito.inputs.UserPoolSoftwareTokenMfaConfigurationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new UserPool("example", UserPoolArgs.builder()
            .mfaConfiguration("ON")
            .smsAuthenticationMessage("Your code is {####}")
            .smsConfiguration(UserPoolSmsConfigurationArgs.builder()
                .externalId("example")
                .snsCallerArn(exampleAwsIamRole.arn())
                .snsRegion("us-east-1")
                .build())
            .softwareTokenMfaConfiguration(UserPoolSoftwareTokenMfaConfigurationArgs.builder()
                .enabled(true)
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:cognito:UserPool
    properties:
      mfaConfiguration: ON
      smsAuthenticationMessage: Your code is {####}
      smsConfiguration:
        externalId: example
        snsCallerArn: ${exampleAwsIamRole.arn}
        snsRegion: us-east-1
      softwareTokenMfaConfiguration:
        enabled: true

Setting mfaConfiguration to “ON” requires all users to provide a second factor. The smsConfiguration block enables SMS codes by specifying an IAM role with SNS permissions and the region for SMS delivery. The softwareTokenMfaConfiguration block enables TOTP codes from authenticator apps. The smsAuthenticationMessage defines the template for SMS codes, where {####} is replaced with the actual code.

Configure password recovery methods

Users who forget passwords need a way to recover access through verified contact methods.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const test = new aws.cognito.UserPool("test", {
    name: "mypool",
    accountRecoverySetting: {
        recoveryMechanisms: [
            {
                name: "verified_email",
                priority: 1,
            },
            {
                name: "verified_phone_number",
                priority: 2,
            },
        ],
    },
});
import pulumi
import pulumi_aws as aws

test = aws.cognito.UserPool("test",
    name="mypool",
    account_recovery_setting={
        "recovery_mechanisms": [
            {
                "name": "verified_email",
                "priority": 1,
            },
            {
                "name": "verified_phone_number",
                "priority": 2,
            },
        ],
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cognito"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cognito.NewUserPool(ctx, "test", &cognito.UserPoolArgs{
			Name: pulumi.String("mypool"),
			AccountRecoverySetting: &cognito.UserPoolAccountRecoverySettingArgs{
				RecoveryMechanisms: cognito.UserPoolAccountRecoverySettingRecoveryMechanismArray{
					&cognito.UserPoolAccountRecoverySettingRecoveryMechanismArgs{
						Name:     pulumi.String("verified_email"),
						Priority: pulumi.Int(1),
					},
					&cognito.UserPoolAccountRecoverySettingRecoveryMechanismArgs{
						Name:     pulumi.String("verified_phone_number"),
						Priority: pulumi.Int(2),
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var test = new Aws.Cognito.UserPool("test", new()
    {
        Name = "mypool",
        AccountRecoverySetting = new Aws.Cognito.Inputs.UserPoolAccountRecoverySettingArgs
        {
            RecoveryMechanisms = new[]
            {
                new Aws.Cognito.Inputs.UserPoolAccountRecoverySettingRecoveryMechanismArgs
                {
                    Name = "verified_email",
                    Priority = 1,
                },
                new Aws.Cognito.Inputs.UserPoolAccountRecoverySettingRecoveryMechanismArgs
                {
                    Name = "verified_phone_number",
                    Priority = 2,
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cognito.UserPool;
import com.pulumi.aws.cognito.UserPoolArgs;
import com.pulumi.aws.cognito.inputs.UserPoolAccountRecoverySettingArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var test = new UserPool("test", UserPoolArgs.builder()
            .name("mypool")
            .accountRecoverySetting(UserPoolAccountRecoverySettingArgs.builder()
                .recoveryMechanisms(                
                    UserPoolAccountRecoverySettingRecoveryMechanismArgs.builder()
                        .name("verified_email")
                        .priority(1)
                        .build(),
                    UserPoolAccountRecoverySettingRecoveryMechanismArgs.builder()
                        .name("verified_phone_number")
                        .priority(2)
                        .build())
                .build())
            .build());

    }
}
resources:
  test:
    type: aws:cognito:UserPool
    properties:
      name: mypool
      accountRecoverySetting:
        recoveryMechanisms:
          - name: verified_email
            priority: 1
          - name: verified_phone_number
            priority: 2

The accountRecoverySetting block defines recovery mechanisms with priority order. When a user requests password recovery, Cognito tries verified_email first (priority 1), then verified_phone_number (priority 2). Users must verify these attributes during registration for recovery to work.

Beyond these examples

These snippets focus on specific user pool features: basic pool creation, MFA configuration (SMS and TOTP), and account recovery mechanisms. They’re intentionally minimal rather than full authentication systems.

The examples may reference pre-existing infrastructure such as IAM roles with SNS permissions for SMS delivery. They focus on configuring the user pool rather than provisioning everything around it.

To keep things focused, common user pool patterns are omitted, including:

  • Password policies (passwordPolicy)
  • Email configuration and SES integration (emailConfiguration)
  • Custom attributes and schema (schemas)
  • Lambda triggers for custom workflows (lambdaConfig)
  • Advanced security features (userPoolAddOns)
  • Username and alias configuration (usernameAttributes, aliasAttributes)

These omissions are intentional: the goal is to illustrate how each user pool feature is wired, not provide drop-in authentication modules. See the Cognito User Pool resource reference for all available configuration options.

Let's create AWS Cognito User Pools

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration Conflicts & Immutability
What properties can't I change after creating a user pool?
aliasAttributes and usernameAttributes are immutable. Schema attributes can be added but never modified or removed. Plan these carefully at creation time.
Can I use both aliasAttributes and usernameAttributes?
No, aliasAttributes and usernameAttributes conflict with each other. Choose one approach for username configuration at creation time.
Why am I getting conflicts with my verification message settings?
emailVerificationMessage conflicts with verificationMessageTemplate.emailMessage, emailVerificationSubject conflicts with verificationMessageTemplate.emailSubject, and smsVerificationMessage conflicts with verificationMessageTemplate.smsMessage. Use one approach or the other, not both.
Multi-Factor Authentication (MFA)
What are the MFA configuration options?
mfaConfiguration has three values: OFF (default, no MFA required), ON (required for all users), or OPTIONAL (per-user MFA). Both ON and OPTIONAL require at least one of emailMfaConfiguration, smsConfiguration, or softwareTokenMfaConfiguration to be configured.
What do I need to configure email MFA?
Email MFA requires at least 2 accountRecoverySetting entries and an emailConfiguration block. It only works when mfaConfiguration is ON or OPTIONAL.
How do I enable both SMS and software token MFA?
Set mfaConfiguration to ON, configure smsConfiguration with your SNS caller ARN and region, and set softwareTokenMfaConfiguration.enabled to true.
SMS Configuration
Why can't I remove SMS configuration from my user pool?
Cognito API restrictions prevent removing smsConfiguration once applied. The resource ignores removal attempts to protect user data. Use the taint command to force recreation if you need to remove it.
What format must my SMS authentication message use?
smsAuthenticationMessage must contain the {####} placeholder, which gets replaced with the verification code.
Schema & Attributes
What happens if I try to modify a schema attribute?
Schema attributes can be added but never modified or removed. You’ll need to recreate the user pool to change existing attributes.
How many schema attributes can I add?
You can configure a maximum of 50 schema attributes. Standard attributes only need specification if they differ from defaults.
Account Recovery & Security
How do I set up account recovery mechanisms?
Configure accountRecoverySetting with recoveryMechanisms, specifying name (verified_email or verified_phone_number) and priority for each mechanism.
How do I protect my user pool from accidental deletion?
Set deletionProtection to ACTIVE. You must change it back to INACTIVE before you can delete the pool. The default is INACTIVE.
What are the available user pool tiers?
User pools support three feature plans: LITE, ESSENTIALS, and PLUS. Configure this using the userPoolTier property.

Using a different cloud?

Explore security guides for other cloud providers: