Amazon Cognito User Poolsをもっと使いこなすためのTips

## 概要
こんにちは！この記事は[Serverless(2) Advent Calendar 2016](http://qiita.com/advent-calendar/2016/serverless2)の11日目の記事です。

ユーザの認証基盤を提供するサービスとして提供されているAmazon Cognito User Poolsは非常に柔軟にカスタマイズが出来る素晴らしいサーバレスなサービスなのですが、Tipsや細かいカスタマイズ方法をGoogleで検索してもあまり出てきません。

もう少し突っ込んだ使い方を書いた記事が世の中にあれば、
もっとこのサービスを皆が便利に使いこなせるようになるのでは、という思いでこの記事を書くことにしました。

それではよろしくお願いします！

## カスタマイズの基礎
僕がNodejsで実装することが多いのでNodeのSDKをベースに説明します。

http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityServiceProvider.html
UserPoolsとのやり取りは上記のCognitoIdentityServiceProviderというSDKを使います。

大枠2種類のAPIが準備されていて、admin~から始まるメソッドとそうでないメソッドの2種類があります。admin~から始まるメソッドは要は管理者向けに用意されたメソッドです。usernameを指定することで様々なユーザの管理作業が可能になります。

そうでないメソッドはaccesstokenを元にしてユーザ自身が操作を行うためのメソッドです。
UserPoolsはユーザのログイン時にaccesstokenが発行されます。アプリケーション内ではそのtokenを引き回して、ログイン状態を管理したりプロフィールのアップデートなど各種操作を可能とします。

https://github.com/aws/amazon-cognito-identity-js
ブラウザアプリケーション用のSDKも用意されています。これはLambda+API Gatewayで作らないといけない処理をすべてラップしてくれているSDKっす。
もちろん、admin~の操作は許可されていません。ブラウザからユーザに管理作業が可能になってしまうので。


## メールアドレスログインを実装する
User Poolsはデフォルトで一意なUserIDをベースにして認証を行う仕組みとなっています。
UserID以外で認証をさせたい場合はエイリアスの機能が提供されています。

<img width="1220" alt="スクリーンショット 2016-12-11 22.54.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/65478/c2c10842-e75e-5c76-8c74-56340a0f1d71.png">

UserPoolを作る際に最初に必要な属性を設定する画面が有ります。
メールアドレスでログインさせたい場合はemailのAliasにチェックを入れてあげましょう。

こうすることでメールアドレスはそのUserPool内で一意なものとなります。そしてログインやパスワード再発行、アクティベーションなどのAPIはすべてAliasで設定したデータを元に行えるようになります。

これ、一度UserPoolを作ってしまうと後から設定変更は出来ませんので注意しましょう。後からメールアドレスログインを実装したいと思っても後の祭りです。。
最初にUserテーブルの設計は帰れない前提で決めて上げる必要があります。

## アクティベーションメールのカスタマイズ
ユーザの登録時やパスワード再発行時にUserpoolから承認用のメールが送信されます。
マネジメントコンソールから文面を編集することは可能なのですが、例えばUserIDをそのメールに記載したりといったことはマネジメントコンソールからは出来ません。

これは`Custom message`というTriggerを使うことで可能です。
以下がサンプルのLambdaファンクションです。アクティベーションメールにUserIDを記載する例です。

```js
'use strict';
exports.handler = (event, context, cb) => {
    event.response.emailMessage = `<p>Hello, ${event.userName}<br />Thanks you for your registration</p>`;
    event.response.emailSubject = 'Activation mail';
    cb(null, event);
};
```

また、eventには以下の様なJSONが格納されています。


```json
{ 
version: '1', 
region: 'us-east-1', 
userPoolId: '<ID>',
userName: '<userid>',
callerContext: { 
  awsSdkVersion: 'aws-sdk-js-2.4.12',
  clientId: '<ID>'
}, 
triggerSource: 'CustomMessage_SignUp',
request: { 
  userAttributes: { 
    email_verified: 'false', 
    'cognito:user_status': 'UNCONFIRMED', 
    email: '<email>' 
  },
  codeParameter: '{####}', 
  usernameParameter: null 
}, 
response: { 
  smsMessage: null, 
  emailMessage: null, 
  emailSubject: null 
}
```

triggerSourceの値にユーザ登録後のメールなのか、メールアドレス変更時のメールなのか、パスワード再発行時のメールなのかのトリガー元の情報も入っています。

これを元にメールの内容を変更したりと言ったことも可能です。

## アクティベーションを行った日付を登録する
ユーザが登録後にアクティベーションを行った日を記載して、その日から~日までは無料でサービスを使えますよ、といった機能が実装できます。

これにはカスタム属性の機能を使います。Attributesの画面からactivationDateというカスタム属性を作ります。
<img width="1085" alt="スクリーンショット 2016-12-11 23.18.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/65478/b631dacb-0add-e752-432e-0ab9ea1a33bb.png">

さらに作った属性には書き込みと読み込みの権限を設定して上げる必要があります。
要はユーザデータとしてユーザ自身に読み込みや書き込みを許可させるのかという設定です。

Appsの画面から以下のように設定可能です。
<img width="865" alt="スクリーンショット 2016-12-11 23.21.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/65478/a39d79b8-5a48-80d8-cc7f-1345200e8dac.png">

カスタム属性を設定すれば、ユーザ登録後のアクティベーションが実施されるタイミングで通るトリガーである`Post confirmation`でカスタム属性にデータ登録するLambdaを設定してあげます。

以下がそのコードです。

```js
'use strict';
const moment = require('moment');
const aws = require('aws-sdk');
aws.config.update({region:'us-east-1'});

exports.handler = (event, context, cb) => {
    console.log({event:JSON.stringify(event)});
    
    const trigger = new userPoolsTriggerPostConfirmation();
    trigger.registerActivatedDate(event.userPoolId, event.userName).then(() => {
      cb(null, event);
    }).catch((error) => {
      console.log(error);
      cb('Internal Server error');
    });
};

class userPoolsTriggerPostConfirmation {
  constructor() {
    this.cognito = new aws.CognitoIdentityServiceProvider();
  }

  registerActivatedDate(userPoolId, userName) {
    const params = {
      UserAttributes: [
        {
          Name: 'custom:activatedDate',
          Value: moment().format('YYYY-MM-DDTHH:mm:ssZ')
        },
      ],
      UserPoolId: userPoolId,
      Username: userName
    };

    return this.cognito.adminUpdateUserAttributes(params).promise()
      .then(() => Promise.resolve({}))
      .catch((error) => Promise.reject(error))
  }
}
```

## ユーザに権限を設定する
いわゆるそのサービス内で権限を実装する仕組みです。これも上記と同様にカスタム属性を使って実装しましょう。

以下の通りroleというカスタム属性を作ります。
<img width="963" alt="スクリーンショット 2016-12-11 23.29.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/65478/562e36e8-5903-3119-4b5f-6aa77db646ce.png">

権限はユーザ自身に読み込みは出来ても書き込みが出来ては行けません。書き込みが出来るとユーザ自身で自由に権限が設定できちゃうので。

<img width="897" alt="スクリーンショット 2016-12-11 23.30.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/65478/a3452e16-49a0-ddbf-29a1-274f6b40e56a.png">

https://github.com/aws/amazon-cognito-identity-js
設定した属性はブラウザアプリケーションでは上記のgetUserAttributesメソッドで

http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityServiceProvider.html#getUser-property
Lambda側で実装する場合は、getUsersを使用しましょう。

roleの値に応じて各権限で可能な処理を分岐させることが出来ます。

## まとめ
というわけで、自身の経験を元にAmazon Cognito User PoolsのカスタマイズTipsをお送りました。カスタマイズのポイントはカスタム属性を如何に使いこなすところかなと思います。

それでは、良いUserPoolsライフを！