【個人開発】WEBサービスのログイン機能をAWS Cognitoで実装する①

# 要件
* サービスのログイン機能をAWSマネージドサービスである　AWS Cognitoを使用して実装する。
* 認証にはユーザID、Eメールアドレスを使用する。
* サインアップ時、Eメールアドレスに検証メッセージを送信する。
* パスワード要件
    * 最小文字数を6文字とする。
    * 少なくとも1つの数字、大文字、小文字を使用する。
 
* 以下の情報をユーザに持たせる。
    * ユーザID
    * Eメールアドレス
    * 誕生日
    * ニックネーム
    * URL　 

# AWS  設定
CloudFormation (cognito構築部分のみ)
```json-doc:CF_congnito.yaml
  MyMovieUserPool:
    Type: AWS::Cognito::UserPool
    Properties: 
      AliasAttributes: 
        - email
      AutoVerifiedAttributes: 
        - email
      Policies: 
        PasswordPolicy: 
          MinimumLength: 6
          RequireLowercase: true
          RequireNumbers: true
          RequireUppercase: true
      Schema: 
        - AttributeDataType: "String"
          Name: "birthdate"
          Required: true
        - AttributeDataType: "String"
          Name: "email"
          Required: true
        - AttributeDataType: "String"
          Mutable: true
          Name: "preferred_username"
          Required: true
        - AttributeDataType: "String"
          Mutable: true
          Name: "website"
          Required: true
      UsernameConfiguration: 
        CaseSensitive: false
      UserPoolName: !Sub mymovie-${EnviromentType}-userpool

  MyMovieUserPoolAppClient:
    Type: AWS::Cognito::UserPoolClient
    Properties: 
      ClientName: MyMovie
      EnableTokenRevocation: false
      PreventUserExistenceErrors: ENABLED
      ExplicitAuthFlows:
        - ALLOW_ADMIN_USER_PASSWORD_AUTH
        - ALLOW_USER_PASSWORD_AUTH
        - ALLOW_USER_SRP_AUTH
        - ALLOW_REFRESH_TOKEN_AUTH
      UserPoolId: !Ref MyMovieUserPool
```
# Signup画面(html)
※以下の例では、jquery,bootstrapを利用しています。
```html:signup.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <!-- viewport meta -->
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Required bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    <!-- Required cognito -->
    <script src="js/cognito/jsbn.js"></script>
    <script src="js/cognito/jsbn2.js"></script>
    <script src="js/cognito/sjcl.js"></script>
    <script src="js/cognito/aws-sdk.min.js"></script>
    <script src="js/cognito/aws-cognito-sdk.min.js"></script>
    <script src="js/cognito/amazon-cognito-identity.min.js"></script>
    <!-- Required jquery -->
    <script src="js/jquery-3.6.0.min.js"></script>
    <!-- Required js/css -->
    <script src="js/signin.js"></script>
  </head>
  <body>
<!-- サインインフォーム -->
      <div id="signup" class="container-fluid w-75 p-3">
        <h1>Sign Up</h1>
        <div id="message"><span style="color: red;"></span></div>
            <form name="form-signup">
                <br><div class="row mb-3">
                <label class="col-sm-2 col-form-label">User ID</label>
                <div class="col-sm-10">
                    <input class="form-control" id="username" placeholder="User ID(半角英数記号のみ)">
                </div>
                </div>
                <br><div class="row mb-3">
                <label class="col-sm-2 col-form-label">Email</label>
                <div class="col-sm-10">
                    <input class="form-control" id="email" placeholder="Email Address">
                </div>
                </div>
                <br><div class="row mb-3">
                  <label class="col-sm-2 col-form-label">NickName</label>
                <div class="col-sm-10">
                    <input class="form-control" id="name" placeholder="Name">
                </div>
                </div>
                <br>
                <div class="row mb-3">
                  <label class="col-sm-2 col-form-label">Birthdate</label>
                  <div class="col-sm-10">
                    <div class="row">
                      <div class="col">
                        <!-- <input type="number" class="form-control" value="2020" aria-label="year" id="year"> -->
                        <select type="number" class="form-control" aria-label="year" id="year">
                        </select>
                      </div>
                      <div class="col">
                        <!-- <input type="number" class="form-control" value="01" aria-label="month" id="month"> -->
                        <select type="number" class="form-control" value="month" aria-label="month" id="month">
                          <option>01</option>
                          <option>02</option>
                          <option>03</option>
                          <option>04</option>
                          <option>05</option>
                          <option>06</option>
                          <option>07</option>
                          <option>08</option>
                          <option>09</option>
                          <option>10</option>
                          <option>11</option>
                          <option>12</option>
                        </select> 
                      </div>
                      <div class="col">
                        <!-- <input type="number" class="form-control" value="01" aria-label="day" id="day"> -->
                        <select type="number" class="form-control" value="day" aria-label="day" id="day">
                        </select>
                      </div>
                    </div>
                  </div>
                </div>
                  <br><div class="row mb-3">
                    <label class="col-sm-2 col-form-label">Password</label>
                  <div class="col-sm-10">
                    <input class="form-control" type="password" id="password" placeholder="Password(英大/小文字/数字必須 6文字以上)">
                  </div>
                  </div>
                  <br>
                <button type="button" class="btn btn-primary" id="createAccount">Create Account</button>
            </form>
      </div>
    </div>
  </body>
</html>
```

# Signupロジック(JavaScript)
```javascript:signup.js
// ユーザープールの設定
var user_pool_id = "{USER_POOL_ID}"
var client_id = "{CLIENT_ID}"
const poolData = {
  UserPoolId : user_pool_id,
  ClientId : client_id
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var attributeList = [];

$(function() {
  // 「Create Account」ボタン押下時
  $("#createAccount").click(function(event) {
  //エラーメッセージ表記初期化
  $("#signup div#message span").empty();
  signUp();
  });
});

// サインアップ処理。
var signUp = function() {
  var username = $("#username").val();
  var email = $("#email").val();
  var name = $("#name").val();
  //誕生日
  var year = $("#year").val();
  var month = $("#month").val();
  var day = $("#day").val();
  var birthdate = year + "-" + month + "-" + day

  var sns = $("#sns").val();
  var password = $("#password").val();

    //空欄チェック
    if (!username | !email | !name | !birthdate | !password) { 
      $("#signup div#message span").append("項目に入力漏れがあります");
      return false;
    }

    //nameの長さチェック
    if (username.length >= 16 || name >= 16) {
      $("#signup div#message span").append("User ID / NickName は16文字までです");
      return false;
    }

    //nameの中身チェック
    var vaildname = /^[a-zA-Z0-9!-/:-@¥[-`{-~]+$/;
    if(!vaildname.test(username)){
      $("#signup div#message span").append("User ID が正しくありません");
      return false;
    }

    //メールアドレスチェック
    var vaildemail = /^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]+.[A-Za-z0-9]+$/;
    if(!vaildemail.test(email)){
      $("#signup div#message span").append("メールアドレスが正しくありません");
      return false;
    }

    //パスワード判定
    var vaildpassword = isValidPassword(password)
    if(!vaildpassword){
      $("#signup div#message span").append("パスワードが正しくありません(英大/小文字/数字を入れた6文字以上)");
      return false;
    }

    // ユーザ属性リストの生成
    var dataEmail = {
      Name : "email",
      Value : email
    }

    var dataName = {
      Name : "preferred_username",
      Value : name
    }

    var dataSNS = {
      Name : "website",
      Value : sns
    }

    var dataBirthdate = {
      Name : "birthdate",
      Value : birthdate
    }

    var attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
    var attributeName = new AmazonCognitoIdentity.CognitoUserAttribute(dataName);
    var attributeSNS = new AmazonCognitoIdentity.CognitoUserAttribute(dataSNS);
    var attributeBirthdate = new AmazonCognitoIdentity.CognitoUserAttribute(dataBirthdate);

    attributeList.push(attributeEmail);
    attributeList.push(attributeName);
    attributeList.push(attributeSNS);
    attributeList.push(attributeBirthdate);
    
    // サインアップ処理
    userPool.signUp(username, password, attributeList, null, function(err, result){
      if (err) {
        $("#signup div#message span").append(err.message);
      return;
      } else {
        window.location.href = './activation.html';
      }
    });  
    .catch(error => {
      $("#signup div#message span").append("登録情報の送信に失敗しました");
    });
}
```

※activation、signin処理は次の記事に続きます。
https://qiita.com/haruya_hamasaki/items/92d5972e46768a902104

# 参考
https://www.tdi.co.jp/miso/amazon-cognito-activation-sign-in

# おまけ
本記事の機能は以下のサービスで利用されています。
気になった方は、お気軽にサービスも利用くださると大変嬉しいです。

#### 観た映画を登録、管理できるサービス　MyMovie
![映画一覧.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374911/8d8ac46a-8305-937f-5225-fb1a834309ad.png)

サービスURL : [mymovie.jp](https://mymovie.jp/)
GitHub : [github.com/hamasakiharuya/mymovie](https://github.com/hamasakiharuya/mymovie)