JavaScriptで、Cognitoコード認証フローを実装したまとめ

# 前書き
ユーザ認証のCognitoを使って、ユーザ認証を実装してみました。
今回はそのまとめです。
実装したフローは、いろんなサービスでよくある以下のフローです。

1. サインアップ
2. 認証コードとURLが届く
3. URL先の認証コード入力画面でコードを入力する

上記のフローを実装して見たので、まとめます。

# 実装
## Cognitoの設定
まずはじめに、Cognito側でメッセージがコードであることを確認します。
![スクリーンショット 2017-10-18 23.37.09.png](https://qiita-image-store.s3.amazonaws.com/0/159013/edfab4e5-4fc6-d24c-13f5-495fd03e179d.png)

あらかじめ、ユーザープールIDとクライアントIDは控えておきましょう。

## コード認証実装
AWS-SDK、およびCognitoを利用するに当たって、以下のライブラリが必要なのでnpmかyarnでインストールします。

```bash
# npm
npm install aws-sdk
npm install amazon-cognito-identity-js
# yarn
yarn add aws-sdk
yarn add amazon-cognito-identity-js
```

2個目は、JavaScript上でCognitoを使いやすくしてくれるライブラリです。

[Github](https://github.com/aws/amazon-cognito-identity-js)

では早速、最初の設定部分と実コード部分を実装していきます。
新規に、util的な扱いとしてcognito.jsを作成します。
長くなるので分けて解説し、最後にまとめて記載します。

### 初期設定

```js
// src/utils/cognito.js

import AWS from 'aws-sdk';
import {
  CognitoUserPool,
  CognitoUser
} from 'amazon-cognito-identity-js';

import * as aws from './../config/aws';

AWS.config.region = aws.AWS_REGION;

AWS.config.update({
  accessKeyId: aws.AWS_ACCESS_KEY,
  secretAccessKey: aws.AWS_SECRET_ACCESS_KEY
});

const poolData = {
  UserPoolId: aws.AWS_COGNITO_USER_POOL_ID,
  ClientId: aws.AWS_COGNITO_APP_CLIENT_ID
};

const userPool = new CognitoUserPool(poolData);
```

AWSのクレデンシャルをAWS.config.updateで設定し
poolDataという形で、プールIDとクライアントIDを設定し、CognitoUserPool関数に渡して
ユーザプールを作成します。
初期設定はこれだけです。

### 1. サインアップ

```js
// Signup
export const signup = (formValue) => {
  return new Promise((resolve, reject) => {
    const attributeList = [
      {
        Name: 'email',
        Value: formValue.email
      }
    ];

    userPool.signUp(formValue.email, formValue.password, attributeList, null, (err) => {
      if (err) {
        reject(err);
      }
      resolve();
    });
  });
};
```

初期設定で作成したuserPoolのsignUpを利用します。
第１引数にemail、第２引数にパスワードを渡して第３引数にはユーザ属性(基本的にemailだけですかね)の配列を渡します。
第４引数は公式をみても、nullを渡していたのでnullを渡します。
第５引数がコールバックになります。
ちなみに、カスタム属性を利用する場合は、Name部が以下になります。

```js
// firstNameがカスタム属性の場合
{
  Name: 'custom:firstName',
  Value: formValue.firstName
}
```

これで、指定したemailにCognitoから認証コードを乗せたメールが届きます。


### 2. 認証コードとURLが届く
デフォルトでは、認証コードのみが届きます。
コードさえあれば、認証は可能ですが
やはりメールに認証コード入力画面へのURLが欲しいと思います。
ここは、別の記事で細かく解説したいですが、Lambdaの作成が必要になります。
作成するLambdaの中身は以下です。

```js
// LambdaHandler
module.exports.sendVerifyMessage = (event, context, callback) => {

  // 指定のUserPoolIdとリクエストのUserPoolIdが同じ
  if (event.userPoolId === process.env.USER_POOL_ID) {
    // トリガーがカスタムメッセージ
      if (event.triggerSource === 'CustomMessage_SignUp') {
        // 実処理
        // 件名
        event.response.emailSubject = "件名";
        // 本文
        event.response.emailMessage = "本文";
      }
  }

  // Return result to Cognito
  context.done(null, event);
};
```
Cognitoが呼び出した際に、eventの中にサインアップ情報が含まれています。
event.response.emailSubjectに件名をemailMessageに本文を入れて返すことでメールが送信されます。
HTMLメールの利用ができるので、簡単にテンプレートを作成して入れるといいです。
eventから取得できる関連するパラメータは以下の通りです。

```js
// メールアドレス
event.userName
// 認証コード
event.request.codeParameter
// そのほかカスタム属性
event.request.userAttributes['custom:hogehoge']
```

入力画面で利用するため、URLの最後にemailをパラメータとして渡す必要があります。
作成したLambdaはトリガー設定のカスタムメッセージへ設定します。

![スクリーンショット 2017-10-19 0.32.41.png](https://qiita-image-store.s3.amazonaws.com/0/159013/9ed52246-16b7-90d7-445d-3e1d1d8c4dd7.png)

### URL先の認証コード入力画面でコードを入力する
認証コード入力画面では、基本的に２回の処理が必要です。

1. パラメータで飛んで来たemailが正しいメールかどうか
2. 認証コードで認証する


それぞれを実装すると以下の形になります。

#### 1. パラメータで飛んで来たemailが正しいメールかどうか

```js
export const checkUser = ((email) => {
  return new Promise((resolve, reject) => {
    if (!email) {
      reject('Reject');
    }
    const userData = {
      Username: email,
      Pool: userPool
    };

    const cognitoUser = new CognitoUser(userData);

    if (!cognitoUser) {
      reject('Reject');
    }

    resolve(cognitoUser);
  });
});
```
パラメータで取得したemailとuserPoolを利用してcognitoUserを利用します。
存在しないユーザの場合はrejectされます。

#### 2. 認証コードで認証する

```js

export const activateUser = ((email, code) => {
  return new Promise((resolve, reject) => {
    const userData = {
      Username: email,
      Pool: userPool
    };

    const cognitoUser = new CognitoUser(userData);
    cognitoUser.confirmRegistration(code, true, (err) => {
      if (err) {
        console.log(err);
        reject(err);
      } else {
        // 成功
        resolve();
      }
    });
  });
});
```

cognitoUserのconfirmRegistrationを利用します。
ここでまたuserDataを利用するので、最初のcheckUserで作成したcognitoUserを再利用してもいいかもしれません。


# まとめ
ここまでで作成したソースを記載してまとめとします。

```js

import AWS from 'aws-sdk';
import {
  CognitoUserPool,
  CognitoUser
} from 'amazon-cognito-identity-js';

AWS.config.region = aws.AWS_REGION;

AWS.config.update({
  accessKeyId: aws.AWS_ACCESS_KEY,
  secretAccessKey: aws.AWS_SECRET_ACCESS_KEY
});

const poolData = {
  UserPoolId: aws.AWS_COGNITO_USER_POOL_ID,
  ClientId: aws.AWS_COGNITO_APP_CLIENT_ID
};

const userPool = new CognitoUserPool(poolData);


// Signup
export const signup = (formValue) => {
  return new Promise((resolve, reject) => {
    const attributeList = [
      {
        Name: 'email',
        Value: formValue.email
      }
    ];

    userPool.signUp(formValue.email, formValue.password, attributeList, null, (err) => {
      if (err) {
        reject(err);
      }
      resolve();
    });
  });
};

// User Check
export const checkUser = ((email) => {
  return new Promise((resolve, reject) => {
    if (!email) {
      reject('Reject');
    }
    const userData = {
      Username: email,
      Pool: userPool
    };

    const cognitoUser = new CognitoUser(userData);

    if (!cognitoUser) {
      reject('Reject');
    }

    resolve(cognitoUser);
  });
});

// Code Activate
export const activateUser = ((cognitoUser, code) => {
  return new Promise((resolve, reject) => {
    cognitoUser.confirmRegistration(code, true, (err) => {
      if (err) {
        console.log(err);
        reject(err);
      } else {
        // 成功
        resolve();
      }
    });
  });
});

```


# 後書き
早口な説明になりましたが、
AWSのSDKがなかなか充実しているので、思ったフローが簡単に実装できました。
サービスのユーザの認証は、
ユーザがサービスの印象を感じる部分でもあるので、しっかり実装していきたいですね。