🚀

もう迷わないCognito×Google連携:TerraformとReact(Vite)で作る認証付きSPA

に公開

目的

  • React(Vite)で「Hello」ページを表示
  • 未ログイン時はCognito Hosted UI経由でGoogleログイン
  • IaCでCognitoを構築し、シークレットはシェル環境変数で安全に管理

完成イメージ

  • フロント: React + Vite + @aws-amplify/ui-reactAuthenticator
  • 認証: Cognito(Hosted UI, Authorization Code + PKCE)+ Google IdP
  • IaC: TerraformでUserPool/Domain/AppClient/Google IdPを作成
  • シークレット: TF_VAR_* 環境変数(シェル)で注入

参考(公式)


手順

1) Terraform でCognitoを構築

  • 作業ディレクトリ例: <PROJECT_ROOT>/infra

変数(infra/variables.tf

variable "aws_region" { type = string, default = "<AWS_REGION>" }        # 例: ap-northeast-1
variable "cognito_domain_prefix" { type = string }                        # 例: "<COGNITO_DOMAIN_PREFIX>"
variable "google_client_id" { type = string, sensitive = true }
variable "google_client_secret" { type = string, sensitive = true }

メイン(infra/main.tf

terraform {
  required_version = ">= 1.13.1"
  required_providers {
    aws    = { source = "hashicorp/aws",    version = "~> 5.0" }
    random = { source = "hashicorp/random", version = "~> 3.6" }
  }
}

provider "aws" { region = var.aws_region }
data "aws_region" "current" {}

resource "random_string" "suffix" { length = 6, upper = false, special = false }

resource "aws_cognito_user_pool" "pool" { name = "<PRODUCT_NAME>-user-pool" }

resource "aws_cognito_user_pool_domain" "domain" {
  domain       = "${var.cognito_domain_prefix}-${random_string.suffix.result}"
  user_pool_id = aws_cognito_user_pool.pool.id
}

resource "aws_cognito_identity_provider" "google" {
  user_pool_id  = aws_cognito_user_pool.pool.id
  provider_name = "Google"
  provider_type = "Google"
  provider_details = {
    client_id        = var.google_client_id
    client_secret    = var.google_client_secret
    authorize_scopes = "openid email profile"
  }
  attribute_mapping = { email = "email" }
}

resource "aws_cognito_user_pool_client" "web" {
  name                                 = "web-client"
  user_pool_id                         = aws_cognito_user_pool.pool.id
  generate_secret                      = false
  allowed_oauth_flows_user_pool_client = true
  allowed_oauth_flows                  = ["code"]
  allowed_oauth_scopes                 = ["openid","email","profile"]
  callback_urls                        = ["http://localhost:<DEV_PORT>/"]  # 例: 5173
  logout_urls                          = ["http://localhost:<DEV_PORT>/"]
  supported_identity_providers         = ["Google"]
  depends_on                           = [aws_cognito_identity_provider.google]
}

output "cognito_hosted_ui_domain" {
  value = "https://${aws_cognito_user_pool_domain.domain.domain}.auth.${data.aws_region.current.name}.amazoncognito.com"
}
output "google_authorized_redirect_uri" {
  value = "https://${aws_cognito_user_pool_domain.domain.domain}.auth.${data.aws_region.current.name}.amazoncognito.com/oauth2/idpresponse"
}
output "user_pool_id"            { value = aws_cognito_user_pool.pool.id }
output "user_pool_web_client_id" { value = aws_cognito_user_pool_client.web.id }

infra/terraform.tfvars(例)

aws_region = "<AWS_REGION>"                     # 例: ap-northeast-1
cognito_domain_prefix = "<COGNITO_DOMAIN_PREFIX>"

2) シークレットはシェルで安全に管理

infra/set-google-creds.sh

#!/bin/bash
# 使い方: source set-google-creds.sh
export TF_VAR_google_client_id="<GOOGLE_CLIENT_ID>"
export TF_VAR_google_client_secret="<GOOGLE_CLIENT_SECRET>"
echo "Google OAuth環境変数を設定しました。"

.gitignore(抜粋)

*.tfstate
.terraform/
*.tfvars
infra/set-google-creds.sh

3) Terraform 適用

cd <PROJECT_ROOT>/infra
source set-google-creds.sh
terraform init
terraform apply -auto-approve
terraform output

出力例(プレースホルダ)

cognito_hosted_ui_domain = "https://<COGNITO_DOMAIN_PREFIX>-<RAND>.auth.<AWS_REGION>.amazoncognito.com"
google_authorized_redirect_uri = "https://<COGNITO_DOMAIN_PREFIX>-<RAND>.auth.<AWS_REGION>.amazoncognito.com/oauth2/idpresponse"
user_pool_id = "<USER_POOL_ID>"
user_pool_web_client_id = "<USER_POOL_WEB_CLIENT_ID>"

4) Google 側の設定

  • OAuthクライアント(Web)で以下を登録
    • Authorized redirect URIs:
      • https://<COGNITO_SUBDOMAIN>.auth.<AWS_REGION>.amazoncognito.com/oauth2/idpresponse
    • Authorized JavaScript origins:
      • http://localhost:<DEV_PORT>(例: 5173)
  • 組織内限定にしたい場合は「同意画面」を“Internal”

5) フロント(Vite + Amplify)を作成

cd <PROJECT_ROOT>
npm create vite@latest <FRONTEND_DIR> -- --template react-ts
cd <FRONTEND_DIR>
npm i
npm i aws-amplify @aws-amplify/ui-react

src/amplifyClient.ts

import { Amplify } from 'aws-amplify';

Amplify.configure({
  Auth: {
    Cognito: {
      userPoolId: '<USER_POOL_ID>',
      userPoolClientId: '<USER_POOL_WEB_CLIENT_ID>',
      loginWith: {
        oauth: {
          domain: '<COGNITO_DOMAIN>',
          scopes: ['openid', 'email', 'profile'],
          redirectSignIn: ['http://localhost:5173/'],
          redirectSignOut: ['http://localhost:5173/'],
          responseType: 'code',
        },
      },
    },
  },
});

src/main.tsx

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import '@aws-amplify/ui-react/styles.css'
import './AmplifyClient'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

src/App.tsx

import { Authenticator } from '@aws-amplify/ui-react';

export default function App() {
  const components = {
    SignIn: {
      FormFields() { return null }, // ユーザー名/パスワード非表示
      Footer() { return null },     // 「Forgot...」非表示
    },
  };

  return (
    <div style={{ minHeight:'100vh', display:'flex', alignItems:'center', justifyContent:'center' }}>
      <Authenticator components={components} socialProviders={['google']} hideSignUp>
        {({ signOut, user }) => (
          <div style={{ padding:24 }}>
            <h1>Hello</h1>
            <p>Signed in as: {user?.username}</p>
            <button onClick={signOut}>Sign out</button>
          </div>
        )}
      </Authenticator>
    </div>
  );
}

src/index.css

/* AuthenticatorでGoogleボタン以外を非表示(区切り線も消す) */
[data-amplify-form] > :not(.federated-sign-in-container),
.federated-sign-in-container > hr { display: none; }
.federated-sign-in-container { padding: 0 !important; }

起動

npm run dev
# http://localhost:<DEV_PORT>

未ログインならHosted UI → Googleへ遷移、ログイン後に「Hello」を表示。


UI周りの調整

https://ui.docs.amplify.aws/react/connected-components/authenticator

セキュリティの要点

  • SPAは「Authorization Code + PKCE」、クライアントシークレットは使わない(ブラウザに出さない)
  • GoogleのAuthorized Redirect URIはCognitoの/oauth2/idpresponse(取り違え注意)
  • 機密値(Googleクレデンシャル)はTF_VAR_*環境変数で注入し、リポジトリに含めない

Discussion