Setting up AWS Cognito with Terraform

A comprehensive guide to configuring Amazon Cognito user authentication using Terraform Infrastructure as Code

Setting up AWS Cognito with Terraform

Amazon Cognito provides authentication, authorization, and user management for web and mobile apps. This guide shows how to set up Cognito using Terraform.

Prerequisites

  • AWS CLI configured
  • Terraform installed
  • Basic understanding of authentication concepts
  • Web or mobile application ready for integration

Project Structure

aws-cognito-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvars

Basic Cognito Configuration

# main.tf
provider "aws" {
  region = var.aws_region
}

# User Pool
resource "aws_cognito_user_pool" "main" {
  name = "${var.project_name}-user-pool"

  username_attributes      = ["email"]
  auto_verified_attributes = ["email"]
  
  password_policy {
    minimum_length    = 8
    require_lowercase = true
    require_numbers   = true
    require_symbols   = true
    require_uppercase = true
  }

  verification_message_template {
    default_email_option = "CONFIRM_WITH_CODE"
    email_subject       = "Account Verification Code"
    email_message      = "Your verification code is {####}"
  }

  schema {
    attribute_data_type = "String"
    name                = "email"
    required            = true
    mutable             = true

    string_attribute_constraints {
      min_length = 7
      max_length = 256
    }
  }

  tags = {
    Environment = var.environment
  }
}

# App Client
resource "aws_cognito_user_pool_client" "main" {
  name = "${var.project_name}-client"

  user_pool_id = aws_cognito_user_pool.main.id
  
  generate_secret     = false
  explicit_auth_flows = [
    "ALLOW_USER_SRP_AUTH",
    "ALLOW_REFRESH_TOKEN_AUTH"
  ]
}

# Identity Pool
resource "aws_cognito_identity_pool" "main" {
  identity_pool_name = "${var.project_name}-identity-pool"

  allow_unauthenticated_identities = false

  cognito_identity_providers {
    client_id               = aws_cognito_user_pool_client.main.id
    provider_name           = aws_cognito_user_pool.main.endpoint
    server_side_token_check = false
  }

  tags = {
    Environment = var.environment
  }
}

Advanced User Pool Configuration

# User Pool with Advanced Features
resource "aws_cognito_user_pool" "advanced" {
  name = "${var.project_name}-advanced-pool"

  # MFA Configuration
  mfa_configuration = "ON"
  
  software_token_mfa_configuration {
    enabled = true
  }

  # Email Configuration
  email_configuration {
    email_sending_account = "DEVELOPER"
    from_email_address    = var.from_email
    source_arn           = var.ses_arn
  }

  # Lambda Triggers
  lambda_config {
    pre_sign_up         = var.pre_signup_lambda_arn
    post_confirmation   = var.post_confirmation_lambda_arn
    pre_authentication  = var.pre_auth_lambda_arn
    post_authentication = var.post_auth_lambda_arn
  }

  # Admin Create User Config
  admin_create_user_config {
    allow_admin_create_user_only = false
    
    invite_message_template {
      email_message = "Your username is {username} and temporary password is {####}."
      email_subject = "Your temporary password"
      sms_message   = "Your username is {username} and temporary password is {####}."
    }
  }

  # Device Configuration
  device_configuration {
    challenge_required_on_new_device      = true
    device_only_remembered_on_user_prompt = true
  }

  tags = {
    Environment = var.environment
  }
}

Variables Configuration

# variables.tf
variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-west-2"
}

variable "project_name" {
  description = "Project name"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

variable "from_email" {
  description = "From email address for Cognito emails"
  type        = string
}

variable "ses_arn" {
  description = "SES ARN for email sending"
  type        = string
}

variable "pre_signup_lambda_arn" {
  description = "Pre-signup Lambda function ARN"
  type        = string
}

Best Practices

  1. User Pool Configuration

    • Use strong password policies
    • Enable MFA when possible
    • Configure proper email verification
    • Use custom attributes wisely
  2. Security

    • Implement proper OAuth flows
    • Use secure authentication methods
    • Monitor user activities
    • Regular security reviews
  3. User Experience

    • Customize email templates
    • Implement proper error handling
    • Use friendly authentication flows
    • Consider device tracking
  4. Cost Optimization

    • Monitor MAU usage
    • Clean up unused pools
    • Use appropriate features
    • Consider pricing tiers

OAuth Configuration

# App Client with OAuth
resource "aws_cognito_user_pool_client" "oauth" {
  name = "${var.project_name}-oauth-client"

  user_pool_id = aws_cognito_user_pool.main.id
  
  generate_secret = true
  
  allowed_oauth_flows = [
    "code",
    "implicit"
  ]
  
  allowed_oauth_flows_user_pool_client = true
  
  allowed_oauth_scopes = [
    "email",
    "openid",
    "profile"
  ]
  
  callback_urls = [
    "https://example.com/callback"
  ]
  
  logout_urls = [
    "https://example.com/logout"
  ]

  supported_identity_providers = [
    "COGNITO"
  ]
}

# Domain Configuration
resource "aws_cognito_user_pool_domain" "main" {
  domain       = var.domain_prefix
  user_pool_id = aws_cognito_user_pool.main.id
}

Identity Pool Roles

# IAM Roles for Identity Pool
resource "aws_iam_role" "authenticated" {
  name = "${var.project_name}-cognito-authenticated"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = "cognito-identity.amazonaws.com"
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringEquals = {
            "cognito-identity.amazonaws.com:aud" = aws_cognito_identity_pool.main.id
          }
          "ForAnyValue:StringLike" = {
            "cognito-identity.amazonaws.com:amr": "authenticated"
          }
        }
      }
    ]
  })
}

resource "aws_iam_role" "unauthenticated" {
  name = "${var.project_name}-cognito-unauthenticated"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = "cognito-identity.amazonaws.com"
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringEquals = {
            "cognito-identity.amazonaws.com:aud" = aws_cognito_identity_pool.main.id
          }
          "ForAnyValue:StringLike" = {
            "cognito-identity.amazonaws.com:amr": "unauthenticated"
          }
        }
      }
    ]
  })
}

# Identity Pool Role Attachment
resource "aws_cognito_identity_pool_roles_attachment" "main" {
  identity_pool_id = aws_cognito_identity_pool.main.id

  roles = {
    authenticated   = aws_iam_role.authenticated.arn
    unauthenticated = aws_iam_role.unauthenticated.arn
  }
}

Deployment Steps

  1. Initialize Terraform:
terraform init
  1. Plan the deployment:
terraform plan
  1. Apply the configuration:
terraform apply

Clean Up

Remove all resources when done:

terraform destroy

Common Use Cases

  1. Social Identity Providers
# Google Identity Provider
resource "aws_cognito_user_pool_identity_provider" "google" {
  user_pool_id  = aws_cognito_user_pool.main.id
  provider_name = "Google"
  provider_type = "Google"

  provider_details = {
    client_id        = var.google_client_id
    client_secret    = var.google_client_secret
    authorize_scopes = "email profile openid"
  }

  attribute_mapping = {
    email    = "email"
    username = "sub"
  }
}

# Facebook Identity Provider
resource "aws_cognito_user_pool_identity_provider" "facebook" {
  user_pool_id  = aws_cognito_user_pool.main.id
  provider_name = "Facebook"
  provider_type = "Facebook"

  provider_details = {
    client_id        = var.facebook_client_id
    client_secret    = var.facebook_client_secret
    authorize_scopes = "email public_profile"
  }

  attribute_mapping = {
    email    = "email"
    username = "id"
  }
}
  1. Custom Resource Server
# Resource Server
resource "aws_cognito_user_pool_resource_server" "main" {
  identifier = "https://api.example.com"
  name       = "${var.project_name}-resource-server"

  user_pool_id = aws_cognito_user_pool.main.id

  scope {
    scope_name        = "read"
    scope_description = "Read access"
  }

  scope {
    scope_name        = "write"
    scope_description = "Write access"
  }
}

User Groups

# User Group
resource "aws_cognito_user_group" "admin" {
  name         = "admin"
  user_pool_id = aws_cognito_user_pool.main.id
  description  = "Managed by Terraform"
  
  precedence   = 1
  role_arn     = aws_iam_role.admin_group.arn
}

resource "aws_cognito_user_group" "users" {
  name         = "users"
  user_pool_id = aws_cognito_user_pool.main.id
  description  = "Managed by Terraform"
  
  precedence   = 2
  role_arn     = aws_iam_role.user_group.arn
}

Conclusion

This setup provides a comprehensive foundation for deploying Cognito using Terraform. Remember to:

  • Plan your authentication strategy carefully
  • Implement proper security measures
  • Configure user flows appropriately
  • Keep your configurations versioned
  • Test thoroughly before production deployment

The complete code can be customized based on your specific requirements and use cases.