CognitoのWebサイトログインをES6で書く

## こういう人向けの記事

* Amazon Cognitoを使いたい
* Webサイトにログイン機能を実装したい
* ES2015（ES6）で書きたい
* WebpackとかBabelとか使いたい
* ReactとかAngularとか使いたい

## Cognitoの素晴らしい点

* 機能がめちゃくちゃ充実（ユーザー認証まわりの機能ほぼ揃ってる）
* 開発コスト大幅削減（自分で作ろうとしたら数週間〜数ヶ月かかる）
* 運用コストゼロ（自動スケール、障害対応不要）
* 他社より安い（5万MAUまで無料、以降も比較的安い）
* AWSコンソール上でユーザー情報一覧を閲覧できる
* 何もしなくても日々進化してくれる

## Cognitoの残念な点

* CognitoのJavaScript対応はまだまだ発展途上
* 不条理な仕様、ハマりどころが多々あることを覚悟する
* ES6、Promise等々を使うなら公式READMEに書かれてるコードはそのまま使えないので注意

日々進化してるのでそのうち解消されるはず

## 事前準備

以下作業が必要。他のQiitaやブログで紹介されているので割愛。

* AWS CLIをインストール
* AWSコンソールでCognitoのアイデンティティプール（認証情報が保存されるDB的な）を作成
* AWSコンソールでCognitoのユーザープール（ユーザー情報が保存されるDB的な）を作成
* そのユーザープールにアプリクライアントを追加（※注：Webブラウザで使用する場合には `クライアントシークレットを生成する` のチェックを外す）

## AWS CLIでユーザーを作る

Web画面上でユーザーを作ると別途JS側からパスワード再設定しなければならないので、CLIで作る。

```
aws cognito-idp sign-up --client-id ＜アプリクライアントID＞ --username ＜ユーザー名（メアド認証の場合はメアド）＞ --password ＜パスワード＞ --user-attributes Name=email,Value=＜メアド＞
aws cognito-idp admin-confirm-sign-up --user-pool-id ＜ユーザープールID＞ --username ＜ユーザー名（メアド認証の場合はメアド）＞
```

## 必要なパッケージをインストール

AWS SDKとAWS Cognito SDKはともに `amazon-cognito-identity-js` をnpm installするだけで内包される。
`amazon-cognito-identity-js` はAWS公式JavaScriptライブラリ。
`Promise.promisify()` を使わない方は `bluebird` は入れなくてもOK。

```
npm i -S bluebird
npm i -S amazon-cognito-identity-js
npm i -D webpack json-loader
```

## ログイン機能を実装

以下コードはサーバーサイドではなくフロントエンドです。
View（HTML/CSS）のコードは割愛しますが、ReactでもAngularでも何でもOK。

```js:UserUtil.js
import Promise from 'bluebird'
import AWS from 'aws-sdk'
import {
  CognitoUserPool,
  CognitoUserAttribute,
  CognitoUser,
  AuthenticationDetails,
} from 'amazon-cognito-identity-js'

const REGION = '＜リージョン＞'
const IDENTITY_POOL_ID = '＜アイデンティティプールID＞'
const USER_POOL_ID = '＜ユーザープールID＞'
const APP_CLIENT_ID = '＜アプリクライアントID＞'
const userPool = new CognitoUserPool({
  UserPoolId: USER_POOL_ID,
  ClientId: APP_CLIENT_ID,
})
export function signIn(Username, Password) {
  const authenticationDetails = new AuthenticationDetails({Username, Password})
  const cognitoUser = new CognitoUser({
    Username,
    Pool: userPool
  })
  return new Promise((resolve, reject)=>{
    cognitoUser.authenticateUser(authenticationDetails, {
      onSuccess: (result)=>{
        resolve(getAccessToken(result))
      },
      onFailure: (err)=>{
        reject(err)
      },
      newPasswordRequired: ()=>{
        reject('Change your password')
      }
    })
  })
}

export function getSession() {
  const cognitoUser = userPool.getCurrentUser()
  if ( !cognitoUser ) return Promise.reject('Current user not found')
  return Promise.promisify(::cognitoUser.getSession)()
    .then((result)=>{
      if ( !result ) return Promise.reject('Session not found')
      return getAccessToken(result)
    })
}

export function getAccessToken(result) {
  return result.getAccessToken().getJwtToken()
}
```

```js:index.js
import * as UserUtil from './UserUtil'

// ログイン
export function signIn() {
  const Username = '＜ブラウザで入力されたユーザー名＞'
  const Password = '＜ブラウザで入力されたパスワード＞'
  UserUtil
    .signIn(Username, Password)
    .then((accessToken)=>{
      // ログイン成功時の処理
    })
    .catch((err)=>{
      // ログイン失敗時の処理
    })
}

// ログイン後のセッション取得
export function getSession() {
  UserUtil
    .getSession()
    .then((accessToken)=>{
      // セッション取得成功時の処理
    })
    .catch((err)=>{
      // セッション取得失敗時の処理
    })
}
```

## 参考にさせていただいたページ
http://qiita.com/Yuki_BB3/items/ee8330830951acd907de
https://github.com/aws/amazon-cognito-identity-js