TerraformPilot

DevOps

Terraform for Mobile App Backends on AWS Amplify (iOS and Android)

Provision mobile app backends with Terraform: AWS Amplify, Cognito auth, AppSync GraphQL, S3 user content, and push notifications via SNS.

LLuca Berton1 min read

Mobile apps on iOS and Android live or die by their backend: auth, sync API, file storage, push, and analytics. Terraform doesn't ship apps to the App Store, but it provisions the entire AWS backend that 90% of mobile apps rely on. This guide shows the canonical stack: Cognito + AppSync + DynamoDB + S3 + SNS + Pinpoint.

Architecture

#
CapabilityAWS service
User authCognito User Pool + Identity Pool
Sync APIAppSync (GraphQL) + DynamoDB
User contentS3 with presigned URLs
PushSNS Mobile Push (APNs + FCM)
AnalyticsPinpoint

Cognito User Pool

#
resource "aws_cognito_user_pool" "users" {
  name = "app-users"
 
  password_policy {
    minimum_length    = 12
    require_lowercase = true
    require_numbers   = true
    require_symbols   = true
    require_uppercase = true
  }
 
  mfa_configuration = "OPTIONAL"
  software_token_mfa_configuration {
    enabled = true
  }
 
  username_attributes      = ["email"]
  auto_verified_attributes = ["email"]
}
 
resource "aws_cognito_user_pool_client" "mobile" {
  name         = "mobile"
  user_pool_id = aws_cognito_user_pool.users.id
 
  generate_secret = false   # mobile apps can't keep secrets
  refresh_token_validity = 30
  access_token_validity  = 60
  id_token_validity      = 60
  token_validity_units {
    refresh_token = "days"
    access_token  = "minutes"
    id_token      = "minutes"
  }
 
  explicit_auth_flows = [
    "ALLOW_USER_SRP_AUTH",
    "ALLOW_REFRESH_TOKEN_AUTH",
  ]
}

AppSync GraphQL API

#
resource "aws_appsync_graphql_api" "api" {
  name                = "app-api"
  authentication_type = "AMAZON_COGNITO_USER_POOLS"
 
  user_pool_config {
    user_pool_id   = aws_cognito_user_pool.users.id
    aws_region     = var.region
    default_action = "ALLOW"
  }
 
  schema = file("${path.module}/schema.graphql")
 
  log_config {
    cloudwatch_logs_role_arn = aws_iam_role.appsync_logs.arn
    field_log_level          = "ERROR"
  }
}
 
resource "aws_appsync_datasource" "todos" {
  api_id           = aws_appsync_graphql_api.api.id
  name             = "todos"
  service_role_arn = aws_iam_role.appsync.arn
  type             = "AMAZON_DYNAMODB"
 
  dynamodb_config {
    table_name = aws_dynamodb_table.todos.name
  }
}

S3 for User Content with Presigned URLs

#
resource "aws_s3_bucket" "user_content" {
  bucket = "app-user-content"
}
 
resource "aws_s3_bucket_cors_configuration" "user_content" {
  bucket = aws_s3_bucket.user_content.id
 
  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET", "PUT", "POST", "HEAD"]
    allowed_origins = ["*"]
    expose_headers  = ["ETag"]
    max_age_seconds = 3000
  }
}

The mobile app calls a Lambda that issues short-lived presigned URLs scoped to users/<sub>/... so users only see their own files.

Push Notifications (APNs + FCM)

#
resource "aws_sns_platform_application" "ios" {
  name                = "ios-app"
  platform            = "APNS"   # APNS_SANDBOX for dev
  platform_credential = file(var.apns_p8_path)
  platform_principal  = var.apns_team_id
}
 
resource "aws_sns_platform_application" "android" {
  name                = "android-app"
  platform            = "GCM"
  platform_credential = var.fcm_server_key
}

Best Practices

#
  • Never put a client secret in a mobile app — Cognito with generate_secret = false + SRP is the safe path.
  • Short access tokens, long refresh tokens — 60 min / 30 days is a good default.
  • Scope S3 keys by Cognito sub in the IAM policy: arn:aws:s3:::bucket/users/$${cognito-identity.amazonaws.com:sub}/*.
  • One AppSync per environment — dev/staging/prod isolation matters more on mobile because rollback means store re-submission.
  • Use Pinpoint, not raw SNS, for marketing pushes — segments, A/B, and analytics ship for free.
#
#Terraform#Mobile#iOS#Android#Amplify

Share this article