React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] 実装編1 Amazon Cognito

こんにちは、広野です。

生成 AI 界隈の技術の進化がすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。

今回 (2回目) は実装編 その1 Amazon Cognito 編です。

大変恐縮ですが、AWS CloudFormation によるデプロイをしているので YAML テンプレートベースでの説明となります。ご了承ください。

前回の記事

アーキテクチャ概要については前回記事で紹介しています。こちらをご覧ください。

 

今回の説明範囲

アーキテクチャ図中、赤枠の部分を説明いたします。

セキュリティの根幹となる認証部分の説明になります。別件で公開している Amazon Cognito の説明記事とほぼ変わりませんが、あらためて掲載します。また、他の用途にも同じ構成で転用しやすいので一度覚えると有用です。ほんと万能です。

実装している仕様

  • 図には Amazon Cognito ユーザープールしか表現していませんが、Amazon Cognito ID プールも作成しています。私が作成するアプリでは ID プールを使用した Amazon Cognito ユーザーまたはグループ単位の AWS リソースアクセス制御をすることが高いからです。ただし、本記事の構成では設定はほぼ何もなく、基本的な設定を入れているだけです。(細かい設定を入れる前の枠だけ作った感じ)
  • Amazon Cognito でユーザーがセルフサインアップできるようにしています。その方がユーザー管理しなくて済み、楽だからです。代わりに、誰でも登録できるのは良くないので、メールアドレスのドメイン名 (本記事では scsk.jp) でセルフサインアップ可否を判断する AWS Lambda 関数を入れています。当然、セルフサインアップの過程でメールの存在確認をするようになっています。
  • Amazon Cognito は管理者用のメール送信をする都合で、Amazon SES を使用しています。そのためのパラメータを入力する必要があります。Amazon SES は以下の参考記事のようにログ取得用の設定セットが作成されていることが前提となっています。
  • ユーザーは MFA を強制されます。
  • Amazon Cognito ユーザーグループは ADMIN と BASIC の 2種類を初期作成しており、後から ADMIN 所属でないとできない機能を作成するときに使用します。バックエンドやアプリ側で、Amazon Cognito ユーザーの属性を取得することで権限制御できます。

 

AWS CloudFormation テンプレート

AWSTemplateFormatVersion: 2010-09-09
Description: The CloudFormation template that creates a Cognito user pool, a Cognito ID pool, Lambda functions and relevant IAM roles.

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  SystemName:
    Type: String
    Description: System name. use lower case only. (e.g. example)
    Default: example
    MaxLength: 10
    MinLength: 1

  SubName:
    Type: String
    Description: System sub name. use lower case only. (e.g. prod or dev)
    Default: dev
    MaxLength: 10
    MinLength: 1

  DomainName:
    Type: String
    Description: Domain name for URL. xxxxx.xxx (e.g. example.com)
    Default: example.com
    MaxLength: 40
    MinLength: 5

  SesId:
    Type: String
    Description: Amazon SES ID for sending emails. (email addreess or domain)
    Default: example.com
    MaxLength: 100
    MinLength: 5

  SesConfigurationSet:
    Type: String
    Description: Amazon SES configuration set for sending emails.
    Default: ses-logs-xxxxxxxxxxxx-ap-northeast-1
    MaxLength: 100
    MinLength: 5

  CognitoAdminAlias:
    Type: String
    Description: The alias name of Cognito Admin email address. (e.g. admin)
    Default: admin
    MaxLength: 100
    MinLength: 5

  CognitoReplyTo:
    Type: String
    Description: Cognito Reply-to email address. (e.g. xxx@xxx.xx)
    Default: xxxxxx@example.com
    MaxLength: 100
    MinLength: 5

  CognitoEmailFrom:
    Type: String
    Description: Cognito e-mail from address. (e.g. xxx@xxx.xx)
    Default: no-reply@mail.example.com
    MaxLength: 100
    MinLength: 5

  AllowedUserEmailDomains:
    Description: Domain list to allow user sign up. Each domains must be comma delimited and double quoted.
    Type: String
    Default: '"scsk.jp"'

Resources:
# ------------------------------------------------------------#
# Cognito Idp Roles (IAM)
# ------------------------------------------------------------#
  CognitoIdpAuthRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${SystemName}-CognitoIdpAuthRole-${SubName}
      Description: This role allows Cognito authenticated users to access AWS resources.
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Federated: cognito-identity.amazonaws.com
            Action:
              "sts:AssumeRoleWithWebIdentity"
            Condition:
              StringEquals:
                "cognito-identity.amazonaws.com:aud": !Ref IdPool
              "ForAnyValue:StringLike":
                "cognito-identity.amazonaws.com:amr": authenticated
      Policies:
        - PolicyName: !Sub ${SystemName}-CognitoIdpAuthRolePolicy-${SubName}
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "mobileanalytics:PutEvents"
                  - "cognito-sync:*"
                  - "cognito-identity:*"
                Resource: "*"

  CognitoIdpUnauthRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${SystemName}-CognitoIdpUnauthRole-${SubName}
      Description: This role allows Cognito unauthenticated users to access AWS resources.
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Federated: cognito-identity.amazonaws.com
            Action:
              "sts:AssumeRoleWithWebIdentity"
            Condition:
              StringEquals:
                "cognito-identity.amazonaws.com:aud": !Ref IdPool
              "ForAnyValue:StringLike":
                "cognito-identity.amazonaws.com:amr": unauthenticated
      Policies:
        - PolicyName: !Sub ${SystemName}-CognitoIdpUnauthRolePolicy-${SubName}
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "mobileanalytics:PutEvents"
                  - "cognito-sync:*"
                  - "cognito-identity:*"
                Resource: "*"

  CognitoGroupAdminRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${SystemName}-CognitoGroupAdminRole-${SubName}
      Description: This role allows Cognito authenticated users that belong to ADMIN group to access AWS resources.
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Federated: cognito-identity.amazonaws.com
            Action:
              "sts:AssumeRoleWithWebIdentity"
            Condition:
              StringEquals:
                "cognito-identity.amazonaws.com:aud": !Ref IdPool
              "ForAnyValue:StringLike":
                "cognito-identity.amazonaws.com:amr": authenticated
      Policies:
        - PolicyName: !Sub example-CognitoGroupBasicPolicy-${SubName}
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "mobileanalytics:PutEvents"
                  - "cognito-sync:*"
                  - "cognito-identity:*"
                Resource: "*"

  CognitoGroupBasicRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${SystemName}-CognitoGroupBasicRole-${SubName}
      Description: This role allows Cognito authenticated users that belong to BASIC group to access AWS resources.
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Federated: cognito-identity.amazonaws.com
            Action:
              "sts:AssumeRoleWithWebIdentity"
            Condition:
              StringEquals:
                "cognito-identity.amazonaws.com:aud": !Ref IdPool
              "ForAnyValue:StringLike":
                "cognito-identity.amazonaws.com:amr": authenticated
      Policies:
        - PolicyName: !Sub ${SystemName}-CognitoIdpUnauthRolePolicy-${SubName}
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "mobileanalytics:PutEvents"
                  - "cognito-sync:*"
                  - "cognito-identity:*"
                Resource: "*"

# ------------------------------------------------------------#
# Lambda (triggered from Cognito) Role (IAM)
# ------------------------------------------------------------#
  LambdaTriggeredFromCognitoRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${SystemName}-LambdaTriggeredFromCognitoRole-${SubName}
      Description: This role grants Lambda functions triggered from Cognito basic priviledges.
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
        - arn:aws:iam::aws:policy/AmazonCognitoPowerUser

# ------------------------------------------------------------#
# Cognito Lambda Invocation Permission
# ------------------------------------------------------------#
  CognitoLambdaInvocationPermissionPresignup:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt LambdaCognitoPresignup.Arn
      Action: lambda:InvokeFunction
      Principal: cognito-idp.amazonaws.com
      SourceAccount: !Ref AWS::AccountId
      SourceArn: !GetAtt UserPool.Arn
    DependsOn:
      - LambdaCognitoPresignup
      - UserPool

  CognitoLambdaInvocationPermissionPostconfirm:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt LambdaCognitoPostconfirm.Arn
      Action: lambda:InvokeFunction
      Principal: cognito-idp.amazonaws.com
      SourceAccount: !Ref AWS::AccountId
      SourceArn: !GetAtt UserPool.Arn
    DependsOn:
      - LambdaCognitoPostconfirm
      - UserPool

# ------------------------------------------------------------#
# Cognito user pool
# ------------------------------------------------------------#
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: !Sub ${SystemName}-${SubName}
      MfaConfiguration: "ON"
      EnabledMfas:
        - SOFTWARE_TOKEN_MFA
      Policies:
        PasswordPolicy:
          MinimumLength: 8
          RequireUppercase: true
          RequireLowercase: true
          RequireNumbers: true
          RequireSymbols: false
          TemporaryPasswordValidityDays: 180
      AccountRecoverySetting:
        RecoveryMechanisms:
          - Name: verified_email
            Priority: 1
      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: false
      AutoVerifiedAttributes:
        - email
      DeviceConfiguration:
        ChallengeRequiredOnNewDevice: false
        DeviceOnlyRememberedOnUserPrompt: false
      EmailConfiguration:
        ConfigurationSet: !Ref SesConfigurationSet
        EmailSendingAccount: DEVELOPER
        From: !Sub "${SystemName}-${SubName} ${CognitoAdminAlias} <${CognitoEmailFrom}>"
        ReplyToEmailAddress: !Ref CognitoReplyTo
        SourceArn: !Sub arn:aws:ses:${AWS::Region}:${AWS::AccountId}:identity/${SesId}
      EmailVerificationMessage: !Sub "${SystemName}-${SubName} Verification code: {####}"
      EmailVerificationSubject: !Sub "${SystemName}-${SubName} Verification code"
      LambdaConfig:
        PreSignUp: !GetAtt LambdaCognitoPresignup.Arn
        PostConfirmation: !GetAtt LambdaCognitoPostconfirm.Arn
      UsernameAttributes:
        - email
      UsernameConfiguration:
        CaseSensitive: false
      UserPoolAddOns:
        AdvancedSecurityMode: "OFF"
      UserPoolTags:
        Cost: !Sub ${SystemName}-${SubName}
  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      UserPoolId: !Ref UserPool
      ClientName: !Sub ${SystemName}-${SubName}-appclient
      GenerateSecret: false
      RefreshTokenValidity: 3
      AccessTokenValidity: 6
      IdTokenValidity: 6
      ExplicitAuthFlows:
        - ALLOW_USER_SRP_AUTH
        - ALLOW_REFRESH_TOKEN_AUTH
      PreventUserExistenceErrors: ENABLED
      SupportedIdentityProviders: 
        - COGNITO
      CallbackURLs:
        - !Sub https://${DomainName}/index.html
      LogoutURLs:
        - !Sub https://${DomainName}/index.html
      DefaultRedirectURI: !Sub https://${DomainName}/index.html
      AllowedOAuthFlows:
        - implicit
      AllowedOAuthFlowsUserPoolClient: true
      AllowedOAuthScopes:
        - email
        - openid

# ------------------------------------------------------------#
# Cognito user group
# ------------------------------------------------------------#
  UserPoolGroupAdmin:
    Type: AWS::Cognito::UserPoolGroup
    Properties:
      Description: ${SystemName} User Group which allows users able to access management tools.
      GroupName: ADMIN
      Precedence: 1
      UserPoolId: !Ref UserPool
      RoleArn: !GetAtt CognitoGroupAdminRole.Arn

  UserPoolGroupBasic:
    Type: AWS::Cognito::UserPoolGroup
    Properties:
      Description: ${SystemName} User Group which allows users able to access foundation and associate contents.
      GroupName: BASIC
      Precedence: 101
      UserPoolId: !Ref UserPool
      RoleArn: !GetAtt CognitoGroupBasicRole.Arn

# ------------------------------------------------------------#
# Cognito id pool
# ------------------------------------------------------------#
  IdPool:
    Type: AWS::Cognito::IdentityPool
    Properties:
      IdentityPoolName: !Sub ${SystemName}-${SubName}
      AllowClassicFlow: false
      AllowUnauthenticatedIdentities: false
      CognitoIdentityProviders:
        - ClientId: !Ref UserPoolClient
          ProviderName: !GetAtt UserPool.ProviderName
          ServerSideTokenCheck: true
      IdentityPoolTags: 
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}
  IdPoolRoleAttachment:
    Type: AWS::Cognito::IdentityPoolRoleAttachment
    Properties:
      IdentityPoolId: !Ref IdPool
      Roles:
        authenticated: !GetAtt CognitoIdpAuthRole.Arn
        unauthenticated: !GetAtt CognitoIdpUnauthRole.Arn
      RoleMappings:
        userpool:
          IdentityProvider: !Sub cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}:${UserPoolClient}
          Type: Token
          AmbiguousRoleResolution: AuthenticatedRole

# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
  LambdaCognitoPresignup:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${SystemName}-CognitoPresignup-${SubName}
      Description: !Sub Lambda Function triggered from Cognito before user self signup to check the user's email domain for ${SystemName}-${SubName}
      Runtime: python3.13
      Timeout: 3
      MemorySize: 128
      Role: !GetAtt LambdaTriggeredFromCognitoRole.Arn
      Handler: index.lambda_handler
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}
      Code:
        ZipFile: !Sub |
          import re
          def lambda_handler(event, context):
            try:
              print(event)
              triggersource = event['triggerSource']
              email = event['request']['userAttributes']['email']
              print(email)
              if triggersource == 'PreSignUp_SignUp':
                print('via self signup')
                domain = email.split('@')[1]
                allowedDomains = [ ${AllowedUserEmailDomains} ]
                if domain in allowedDomains:
                  if re.fullmatch(r'w[0-9]{5}@scsk\.jp',email):
                    print('prohibited email account')
                    return None
                  else:
                    print('allowed domain and email account')
                    return event
                else:
                  print('prohibited domain')
                  return None
              else:
                print('via admin console')
                return event
            except Exception as e:
              print(str(e))
    DependsOn: LambdaTriggeredFromCognitoRole

  LambdaCognitoPostconfirm:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${SystemName}-CognitoPostconfirm-${SubName}
      Description: !Sub Lambda Function triggered from Cognito after user confirmation to add the user in BASIC group for ${SystemName}-${SubName}
      Runtime: python3.13
      Timeout: 3
      MemorySize: 128
      Role: !GetAtt LambdaTriggeredFromCognitoRole.Arn
      Handler: index.lambda_handler
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}
      Code:
        ZipFile: |
          import boto3
          client = boto3.client('cognito-idp')
          def lambda_handler(event, context):
            USERPOOLID = event['userPoolId']
            USERNAME = event['userName']
            try:
              print(event)
              res = client.admin_add_user_to_group(
                UserPoolId=USERPOOLID,
                Username=USERNAME,
                GroupName='BASIC'
              )
              return event
            except Exception as e:
              print(str(e))
    DependsOn: LambdaTriggeredFromCognitoRole

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
# Cognito
  CognitoUserPoolID:
    Value: !Ref UserPool
    Export:
      Name: !Sub CognitoUserPoolId-${SystemName}-${SubName}
  CognitoArn:
    Value: !GetAtt UserPool.Arn
    Export:
      Name: !Sub CognitoArn-${SystemName}-${SubName}
  CognitoProviderName:
    Value: !GetAtt UserPool.ProviderName
    Export:
      Name: !Sub CognitoProviderName-${SystemName}-${SubName}
  CognitoProviderURL:
    Value: !GetAtt UserPool.ProviderURL
    Export:
      Name: !Sub CognitoProviderURL-${SystemName}-${SubName}
  CognitoAppClientID:
    Value: !Ref UserPoolClient
    Export:
      Name: !Sub CognitoAppClientId-${SystemName}-${SubName}
  CognitoIdPoolID:
    Value: !Ref IdPool
    Export:
      Name: !Sub CognitoIdPoolId-${SystemName}-${SubName}

 

続編記事

続編記事が出来次第、この章を更新します。

 

まとめ

いかがでしたでしょうか。

まだ本来の目的を達成するための前段階ですが、別件でも役に立つ構成だと思いますので改めて載せました。

本記事が皆様のお役に立てれば幸いです。

著者について
広野 祐司

AWS サーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリとコンテンツづくりに勤しんでいます。React で SPA を書き始めたら快適すぎて、他の言語には戻れなくなりました。サーバーレス & React 仲間を増やしたいです。AWS は好きですが、それよりもバックエンド構築を簡単にしてくれたことに対する感謝の気持ちの方が強いです。
取得資格:AWS 認定は15資格、IT サービスマネージャ、ITIL v3 Expert 等
2020 - 2024 Japan AWS Top Engineer 受賞
2022 - 2024 AWS Ambassador 受賞
2023 当社初代フルスタックエンジニア認定
好きなAWSサービス:AWS Amplify / AWS AppSync / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする

クラウドに強いによるエンジニアブログです。

SCSKクラウドサービス(AWS)は、企業価値の向上につながるAWS 導入を全面支援するオールインワンサービスです。AWS最上位パートナーとして、多種多様な業界のシステム構築実績を持つSCSKが、お客様のDX推進を強力にサポートします。

AI・MLAWSアプリケーション開発クラウドソリューション
シェアする
タイトルとURLをコピーしました