【AWS×Webアプリ】CloudFront+Cognito認証（SAM）

## 目的
・AWS上の静的Webサイトホスティングを有効にしたS3をCloudFrontで公開。
・Cognito認証を実装。

## 前提条件
・SAMを使用してAWS上にリソースを作成する。
・Lambda@Edgeを使用して実装する。

## 完成イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/b40ed51f-5720-da27-f7dd-6fc8758e8ed7.png)


## SAMテンプレート
```yaml:template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:

  # ... 他のリソースは変更なし ...
  
  WebBucketDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        DefaultCacheBehavior:
          TargetOriginId: WebBucketOrigin
          Compress: true
          ViewerProtocolPolicy: redirect-to-https
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          LambdaFunctionAssociations:
            - EventType: viewer-request
              LambdaFunctionARN: !Ref CognitoLambdaEdge.Version
          CachePolicyId: !Ref WebBucketCachePolicy
          OriginRequestPolicyId: !Ref WebBucketOriginRequestPolicy
        PriceClass: PriceClass_200
        DefaultRootObject: index.html
        Enabled: true
        Origins:
          - DomainName: !GetAtt WebBucket.RegionalDomainName
            Id: WebBucketOrigin
            OriginAccessControlId: !GetAtt WebBucketOriginAccessControl.Id
            S3OriginConfig:
              OriginAccessIdentity: ''
              
  # ... 他のリソースは変更なし ...
  
  CognitoUserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: !Sub ${AWS::StackName}-user-pool
      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: false
      AliasAttributes:
        - email
        - preferred_username
      AutoVerifiedAttributes:
        - email
      EnabledMfas:
        - SOFTWARE_TOKEN_MFA
      MfaConfiguration: 'ON'
      Policies:
        PasswordPolicy:
          MinimumLength: 8
          RequireUppercase: true
          RequireLowercase: true
          RequireNumbers: true
          RequireSymbols: true
          TemporaryPasswordValidityDays: 7
      Schema:
        - Name: email
          AttributeDataType: String
          Mutable: true
          Required: true
        - Name: preferred_username
          AttributeDataType: String
          Mutable: true
          Required: false
        - Name: name
          AttributeDataType: String
          Mutable: true
          Required: true
          StringAttributeConstraints:
            MinLength: '1'
            MaxLength: '100'
      VerificationMessageTemplate:
        DefaultEmailOption: CONFIRM_WITH_LINK
        EmailSubject: メールアドレスの確認
        EmailMessage: 'あなたのメールアドレスを確認するには、以下のリンクをクリックしてください: {####}'
  CognitoUserPoolDomain:
    Type: AWS::Cognito::UserPoolDomain
    Properties:
      Domain: !Sub ${AWS::StackName}-${AWS::AccountId}
      UserPoolId: !Ref CognitoUserPool
  CognitoUserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      ClientName: !Sub ${AWS::StackName}-client
      UserPoolId: !Ref CognitoUserPool
      CallbackURLs:
        - !Sub https://${WebBucketDistribution.DomainName}
      SupportedIdentityProviders:
        - COGNITO
      AllowedOAuthFlows:
        - code
      AllowedOAuthFlowsUserPoolClient: true
      AllowedOAuthScopes:
        - openid
  CognitoLambdaEdgeRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::StackName}-CognitoLambdaEdgeRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
                - edgelambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: CognitoLambdaEdgePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
              - Effect: Allow
                Action:
                  - lambda:GetFunction
                  - lambda:EnableReplication*
                  - iam:CreateServiceLinkedRole
                Resource: '*'
  CognitoLambdaEdge:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: CognitoLambdaEdge
      CodeUri: lambda/CognitoLambdaEdge
      Handler: index.handler
      Runtime: nodejs18.x
      Timeout: 5
      Tracing: Active
      AutoPublishAlias: live
      Role: !GetAtt CognitoLambdaEdgeRole.Arn
Outputs:
  WebsiteURLonCF:
    Description: URL for website hosted on CloudFront
    Value: !GetAtt WebBucketDistribution.DomainName
```

①WebBucketDistribution（CloudFrontディストリビューション）
・LambdaFunctionAssociations：Lambda@Edgeとの関連付け

②CognitoUserPool（Cognitoユーザプール）
・AdminCreateUserConfig：ユーザがサインアップできるよう設定（AllowAdminCreateUserOnly: false）
・AliasAttributes：ユーザが「user_name」以外でのサインインを可能にする
（email, preferred_username）
・AutoVerifiedAttributes:ユーザー登録時に自動的に検証される属性を指定（email）
・EnabledMfas：User Pool で利用可能な多要素認証（MFA）の方法を指定（SOFTWARE_TOKEN_MFA: TOTP (Time-based One-Time Password) アプリを使用した MFA）
・MfaConfiguration：ユーザプール全体の MFA（多要素認証）ポリシーを定義
・PasswordPolicy：パスワードポリシーの設定
　a. 最小長の設定（MinimumLength: 8）
　b. 大文字必須（RequireUppercase: true）
　c. 小文字必須（RequireLowercase: true）
　d. 数字必須（RequireNumbers: true）
　e. 記号必須（RequireSymbols: true）
　f. 一時パスワードの有効期限（日数）（TemporaryPasswordValidityDays: 7）
・Schema：ユーザープロファイルに保存される属性（フィールド）を定義
　a. Name: 属性の名前
  b. AttributeDataType: データ型（String, Number, DateTime, Boolean など）
  c. Mutable: 属性の変更可否
  d. Required: 属性が必須かどうか
  e. StringAttributeConstraints: 文字列属性の制約（最小/最大長など）
  f. NumberAttributeConstraints: 数値属性の制約（最小/最大値など）
・VerificationMessageTemplate：検証のために送信されるメッセージのカスタマイズ

③CognitoUserPoolDomain（Cognitoユーザプールドメイン）

④CognitoUserPoolClient（Cognitoユーザプールクライアント）
・CallbackURLs：認証プロセス完了後、Cognito が認証コードまたはトークンをリダイレクトする先のURL
・SupportedIdentityProviders：ユーザプールクライアントが使用できる認証プロバイダーを指定
・AllowedOAuthFlows：クライアントアプリケーションが使用できる OAuth 2.0 認証フローを定義
・AllowedOAuthFlowsUserPoolClient：ユーザプールクライアントに対して OAuth 2.0 フローの使用を許可（= true）
・AllowedOAuthScopes：クライアントアプリケーションがアクセスを要求できる OAuth スコープを指定

## Lambda
```index.js
const { Authenticator } = require('cognito-at-edge');

const authenticator = new Authenticator({
  region: 'user pool regionを指定',
  userPoolId: 'user pool IDを指定',
  userPoolAppId: 'user pool app client IDを指定',
  userPoolDomain: 'user pool domainを指定',
});

exports.handler = async (request) => authenticator.handle(request);
```

## 動作確認
①Webサイトへアクセス
・サインイン画面が表示されることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/4a6b48aa-5d26-a95e-608a-1f9d21367bb9.png)

②サインアップ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/b8f86250-f9ef-a306-6252-1eb35db5e43d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/a05d61d0-0881-a1a0-92ae-d06f375980d4.png)

③メールアドレスの検証
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/c1700e1f-cf5a-bc0b-f7cc-390bc4599c8f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/088976bc-885c-b57b-8dc2-09d667f0adce.png)

④サインイン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/3d302b00-d401-4684-f8a8-3d0925197767.png)

⑤MFAの登録
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/5df906ca-3a53-cc30-c1e6-c35945268f43.png)

⑥Webサイトへアクセス（完了）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/302cc576-16c8-5e19-9b59-9c5250500095.png)

⑦MFA（次回サインイン後から）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/3824153a-4359-94b1-abb6-ffb553878db0.png)

## 参考（前回記事）
https://qiita.com/Haruki-N/items/f9e8fb32fd20285b57ad