AWS+Reactアプリ作成入門(Cognito編)

[AWS+Reactアプリ作成入門(Cognito編)](https://qiita.com/sand/items/e1f4ec830d13569a7128 "AWS+Reactアプリ作成入門(Cognito編)")
[AWS+Reactアプリ作成入門(S3編）](https://qiita.com/sand/items/94cd85c3c0426dc768bd "AWS+Reactアプリ作成入門(S3編）")
[AWS+Reactアプリ作成入門(DynamoDB編）](https://qiita.com/sand/items/70018f322d6fe986fe60 "AWS+Reactアプリ作成入門(DynamoDB編）")
[AWS+Reactアプリ作成入門(IAM Role編）](https://qiita.com/sand/items/a07a08f7c1ccc2e8b967 "AWS+Reactアプリ作成入門(IAM Role編）")
[AWS+Reactアプリ作成入門(ログイン後のAdmin編)](https://qiita.com/sand/items/260a6a0b360e574642cf "AWS+Reactアプリ作成入門(ログイン後のAdmin編)")


今回作成したアプリ ==>[久喜SNS](http://kuki-app-bucket.s3-website-ap-northeast-1.amazonaws.com/ "久喜SNS")

　AWSを使って簡単なWebアプリを作り、結構苦労したところがありましたのでその部分を中心に備忘録もかねて書いていきたいと思います。~~個人的には、Webアプリを作るのはMeteor+Reactの環境が最強だと思っていますが、~~（2018年12月08日現在、Phoenix/Elixir+Elmが最強かなと思っています。）　画像の保存やシステム環境の信頼性を考えればAWSの利用も強力な選択肢です。ただ使ってみた感想はCognitoの認証やDynamoDBは癖が多くてハードルが高かったです。一度覚えてしまえば問題はないと思いますが。

　作ったアプリは「久喜SNS」です。Reactアプリですが、create-react-appでLinux環境で作成し、テストしてからAWSのS3にdeployします。S3はファイルの保管場所のようなものですが、HTMLやCSS、JavaScriptファイルを置いて静的ホスティングサービスも提供してくれます。詳しくは続編の「S3編」で述べます。


　まず初めにAWSを使う理由です。いろいろありますので列挙します。
1.  何といっても信頼性があること
2.  使い方によっては低価格で抑えられること
3.  サーバレス環境はReactのSPAと相性が良いと思われること
4.  一度覚えれば次からは簡単にアプリが作成できると予想できること





【2018/11/19追記】AWSの全体的なcognito関連のまとめ記事[「JavaScriptからAmazon Cognitoを使うためのまとめ - console.lealog();」](https://lealog.hateblo.jp/entry/2017/01/25/105528)がわかりやすい。




# ０．アプリの全体像
　今回は画像掲示板のSNSのようなものを作成しました。以下のような機能を持っています。
1.  久喜市の最新情報を画像とマップでお知らせする。
2.  画像を投稿すると自動的にサムネイルが作成され一覧表示に使われる。
3.  メールアドレスでユーザ登録すると、ログインして誰でも利用可能になる。
4.  投稿には非ログインユーザもコメントを付けられる。
5.  自分の投稿は編集・削除ができる。

　技術的なところでは、ザックリ言ってシンプルなReactアプリを作成します。create-react-appを使ってプロジェクトを作成し、プログラム作成確認を行ってから、AWSのS3環境にdeployします。Reactアプリはブラウザで動作しますが、必要に応じてAWSのS3やDynamoDBにアクセスします。ユーザはCognito認証で非ログインとログインの２つの権限のどちらかでAWSリソースにアクセスします。以下のようなAWSのサービスを組み合わせて使います。


1.　Cognito
　　ユーザの管理はユーザプールで行います。ユーザ認証はフェデレーテッドアイデンティティで行います。ログインユーザと非ログインユーザの権限を表すRoleとの紐付けを行います。
2.　IAM
　　以下の４つのRoleの定義を行います。
　　・ログインユーザのアクセス権限を定義したもの
　　・非ログインユーザのアクセス権限を定義したもの
　　・ユーザプールでの確認メールの権限を定義したもの
　　・サムネイル生成のLamda関数の権限を定義したもの
3.　S3
　　画像の保管場所とReactアプリ(HTML,CSS,JavaScriptファイル)の保管場所に使われます。静的ホスティングサービスを有効にします。キャッシュファイルの保管にも使います。
4.　DynamoDB
　　画像掲示板の投稿とコメントの投稿用に２つのテーブルを作成します
5.　Lambda関数
　　画像が投稿されたタイミングで動作する関数です。自動的にサムネイル画像を生成しS3に保管します。



　今回は特にユーザ管理・認証(Cognito)について書きたいと思います。ちょうど前回はMeteorに関して同様なテーマを扱いましたので、大筋をなぞりたい思います。大きな違いは、今回のアプリは基本的にサーバではなくクライアントだけで動作します。ブラウザで動くReactアプリです。唯一の例外はサムネイル画像を作成するlambda関数の存在です。画像をアップロードしたタイミングで起動される黒子的な関数です。
https://qiita.com/sand/items/34bea25a3600069894c6

1. ユーザプールの作成
2. 非ログインユーザ
3. ユーザ登録（メール確認あり）
4. パスワード再発行
5. ログイン
6. ログアウト
7. パスワード変更

　まず基本的なプロジェクトを作成し必要なパッケージをインストールします。

```sh:
create-react-app kuki-app
cd kuki-app
npm install --save react react-dom prop-types react-router react-router-dom
npm install --save material-ui　leaflet　react-leaflet
npm install --save amazon-cognito-identity-js
```
　特にamazon-cognito-identity-jsは重要です。Cognito認証のために使われます。【2018/11/19】リンク修正
https://github.com/aws-amplify/amplify-js/tree/master/packages/amazon-cognito-identity-js


　後はmaterial-uiにデザインを任せ、leafletでマップを表示します。地域SNSのつもりなのでマップは必要です。leafletは以下の記事でも利用しています。
https://qiita.com/sand/items/02219ec5e42cc406ee5c



# １．ユーザプールの作成
　ユーザ登録時にメールを送り検証リンクをクリックさせることで、登録のメールアドレスを検証します。検証済みのメールアドレスでログインができるようになります。この説明は少々長いですので、以下の記事も参考にしてください。
https://qiita.com/horike37/items/1d522f66452d3abe1203
https://dev.classmethod.jp/cloud/aws/cognito-user-pool/

AWSコンソールで以下の作業をします。

（１）ユーザプール作成
　これはユーザ管理を行うものです。上記サイトを参考にしながらステップを踏んで作成していきます。属性はemailとname(username)を指定し、emailでログインするようにします。最後にアプリクライアントを作成しますが、「クライアントシークレットを生成する」のチェックを外します。これでプール IDとアプリクライアントIDが得られます。

　また「ユーザーに自己サインアップを許可しますか?」には「ユーザーに自己サインアップを許可する」を指定します。これでユーザ登録にsignUp()が使えるようになり、メールを送信してメールアドレスの検証を行うことができるようになります。「管理者のみがユーザーを作成できます」を選択するとsignup()ではなくadminCreateUser()を使うことになり、ユーザ登録時にメールアドレスの検証を行わないことになります。

　ちなみにemailの検証タイプをコードからリンクに変更する場合は、ドメイン名を指定する必要があります。私の場合は「kuki-app」と指定しました。


（２）フェデレーテッドアイデンティティの作成
　これは認証を行うためのものです。IDプールを作成する時に認証されていないIDも有効にします。上で作成したユーザプールと関連付けるために認証プロバイダーとしてCognitoを選び、プール IDとアプリクライアントIDを設定します。
　ちなみにCognitoの強みは認証プロバイダーとしてFacebookやGoogle+、Twitterの認証を利用できることですが、ここでは使いません。

【公式サイト】[Amazon Cognito ID プール (フェデレーティッドアイデンティティ)](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-identity.html)

```
Amazon Cognito ID プール (フェデレーテッドアイデンティティ) では、
ユーザーの一意の ID を作成し、ID プロバイダーで連携させることができます。
ID プールにより、権限が制限された一時的な AWS 認証情報を取得して、
別の AWS サービスにアクセスできます。
Amazon Cognito アイデンティティプールでは、
次のアイデンティティプロバイダをサポートしています。

・パブリックプロバイダー: Login with Amazon (ID プール)、
　　　　　　　　　　　　　Facebook (ID プール)、Google (ID プール)。
・Amazon Cognito ユーザープール   ★★★ 今回はこれ
・Open ID Connect プロバイダー (ID プール)
・SAML ID プロバイダー (ID プール)
・開発者が認証した ID (ID プール)
```


（３）ロールの設定
　IDプールを作成した後に、「認証されていないロール」と「認証されたロール」のロールを編集する必要があります。これは非ログインユーザやログインユーザが、S3やDynamoDBなどの必要なリソースにアクセスするために権限を付与するために行います。これはアプリによって異なるものなので、この時点では詳しくは述べません。
[AWS+Reactアプリ作成入門(IAM Role編）](https://qiita.com/sand/items/a07a08f7c1ccc2e8b967 "AWS+Reactアプリ作成入門(IAM Role編）")

　登録されたユーザは、特にテーブルとかの作成も必要なく、AWSコンソールで閲覧でき削除もできますので便利ですね。

　なれればなんでもないことなのかもしれませんが、ここまででもめんどいですね。


# ２．非ログインユーザ
　フェデレーテッドアイデンティティの作成で、認証されていないIDも有効にしたので、非ログインユーザもAWSリソースにアクセスする権限を持つことができます。そのための設定はApp.jsで行っています。
[AWS+Reactアプリ作成入門(IAM Role編）](https://qiita.com/sand/items/a07a08f7c1ccc2e8b967 "AWS+Reactアプリ作成入門(IAM Role編）")

　まず「１．ユーザプールの作成」で説明した設定のConfigファイルを以下に示します。

```js:src/appConfig.js
const appConfig = {
  region: 'ap-northeast-1',
  IdentityPoolId: 'ap-northeast-1:xxxxxxxxxxxxxxxxx',
  UserPoolId: 'ap-northeast-1_xxxxxxxxxx',
  ClientId: 'xxxxxxxxxxxxxx',
};
export default appConfig;
```

　次にメインのApp Componentのソースコードの一部を示します。ここではまず非ログインユーザのためのCognitoの設定を行っています。App ComponentではReact-RouterのRouteの定義を行っています。全ComponentへのPathが定義されています。ここでのCognitoの設定は全Componentで有効になります。

```js:src/App.js
import AWS from "aws-sdk";
import appConfig from './appConfig';

---

AWS.config.region = appConfig.region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: appConfig.IdentityPoolId
});

---

export default class App extends React.Component {

---

  render () {
    // ログイン状態の確認はemailで行う
    let email = null;
    if( AWS.config.credentials.expired ) { //明示的なログアウトせずにcredentialsの期限切れを迎えた
        localStorage.removeItem('email');
    } else {
        email = localStorage.getItem('email');
    }
    const identityId = AWS.config.credentials.identityId;
    let errorSnackbarJSX = <Snackbar
        open={!!this.state.errorValue}
        message={this.state.errorValue?this.state.errorValue:""}
        autoHideDuration={8000}
        onRequestClose={ () => {console.log('You can add custom onClose code'); this.setState({errorValue:null});} } />;

    let homeLinksJSX = (
    <IconButton tooltip="go to home">
        <Link to='/'><ActionHome /></Link>
    </IconButton>);

     let menuLinksJSX=null;
     let bottomLinksJSX=null;
     if( !email ) {
         menuLinksJSX = 
           (<span>
              <Link to='/register'><RaisedButton label="登録" /></Link> 
              <Link to='/login'><RaisedButton label="ログイン" /></Link> 
              <Link to='/recover'><RaisedButton label="再発行" /></Link> 
            </span>);
          bottomLinksJSX =
            (<Link to='/login' style={{textAlign: 'right'}}>
                <BottomNavigationItem label="ログイン" icon={loginIcon}/>
             </Link>);

     } else {
         menuLinksJSX = 
           (<span>
              <Link to='/admin'><RaisedButton label="管理" /></Link> 
              <Link to='/logout'><RaisedButton label="ログアウト" /></Link> 
              <Link to='/change-password'><RaisedButton label="変更" /></Link> 
            </span>);

          bottomLinksJSX =
            (<Link to='/logout' style={{textAlign: 'right'}}>
                <BottomNavigationItem label="ログアウト" icon={logoutIcon}/>
             </Link>);
     }

    return (
      <div>
        <AppBar
            title='久喜SNS'
            iconElementLeft={homeLinksJSX}
            iconElementRight={menuLinksJSX} />

        <div>
          {errorSnackbarJSX}
          <Switch>
            <Route exact path='/' component={Home} name='home' />
            <Route path='/register' component={RegisterView} name='register' />
            <Route path='/login' component={LoginView} name='login' />
            <Route path='/logout' component={LogoutView} name='logout' />
            <Route path='/recover' component={RecoverView} name='recover' />
            <Route path='/change-password' component={ChangePasswordView} name='change' />
            <Route path='/post' component={Post} name='post' />
            <Route path='/admin'  render={ () => (
                (!!email) ? (
                    <Admin />
                ) : (
                    <Redirect to="/"/>
                )
            )}/>
            <Route component={PageNotFound}/>
          </Switch>
        </div>

        <br /><br /><br /><br />
        <Paper zDepth={1}>
          <BottomNavigation selectedIndex={this.state.selectedIndex}>
            <Link to='/'>
              <BottomNavigationItem label="トップページ" icon={homeIcon}/>
            </Link>
            {bottomLinksJSX}
          </BottomNavigation>
        </Paper>

      </div>
    );
  }
}
```

　以下のコードでCognitoの非ログインユーザの権限（Role）を定義します。これでApp Componentだけでなく他の子Componentも必要なリソースにアクセスできます。これに対応したログインユーザのコードを「5. ログイン」で示します。

```js
AWS.config.region = appConfig.region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: appConfig.IdentityPoolId
});
```

　画面トップのアプリメニューはログイン／ログアウトで切り替わります。ログインしたときにlocalStorageにemailを登録し、ログアウトしたときにemailを削除しますので、emailでログイン状態の確認ができます。



# ３．ユーザ登録（メール確認あり）

 次にユーザ登録のcomponentの全ソースを載せます。

```js:src/views/RegisterView.js
import {Config, CognitoIdentityCredentials} from "aws-sdk";
import {
  CognitoUserPool,
  CognitoUserAttribute,
  CognitoUser,
  AuthenticationDetails
} from "amazon-cognito-identity-js";
import React from 'react';
import { handleErrorFunc } from '../App';
import TextField from 'material-ui/TextField';
import Paper from 'material-ui/Paper';
import appConfig from '../appConfig';

const userPool = new CognitoUserPool({
  UserPoolId: appConfig.UserPoolId,
  ClientId: appConfig.ClientId,
});

export default class RegisterView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      email: "",
      username: "",
      password: ""
    };
  }

  handleEmailChange(e) {
    this.setState({email: e.target.value});
  }
  handleNameChange(e) {
    this.setState({username: e.target.value});
  }
  handlePasswordChange(e) {
    this.setState({password: e.target.value});
  }

  handleSubmit(e) {
    e.preventDefault();
    const email = this.state.email.trim();
    const username = this.state.username.trim();
    const password = this.state.password.trim();
    const attributeList = [
      new CognitoUserAttribute({
        Name: 'email',
        Value: email,
      }),
      new CognitoUserAttribute({
        Name: 'name',
        Value: username,
      })
    ];
    userPool.signUp(email, password, attributeList, null, (err, result) => {
      if (err) {
        console.log(err);
        return;
      }
      console.log('user name is ' + result.user.getUsername());
      console.log('call result: ' + result);
      this.setState({email: ""});
      this.setState({username: ""});
      this.setState({password: ""});
    });
  }

  render () {
    const style = {
      width: '80%',
      margin: 50,
      padding: 50,
      display: 'inline-block',
    };
    return (
      <div>
        <Paper style={style} zDepth={1}>
          <h2 style={{textAlign: 'left'}}>ユーザ登録</h2>
          <form onSubmit={this.handleSubmit.bind(this)}>
            <TextField fullWidth={true}
                 value={this.state.email}
                 hintText="Email"
                 onChange={this.handleEmailChange.bind(this)}/><br />
            <TextField fullWidth={true}
                 value={this.state.username}
                 hintText="名前（表示名、お好きなもの）"
                 onChange={this.handleNameChange.bind(this)}/><br />
            <TextField fullWidth={true}
                 value={this.state.password}
                 hintText="パスワード（英数字で8文字以上。大文字と小文字、数字を必ず混ぜてください。）"
                 onChange={this.handlePasswordChange.bind(this)}/><br />
            <input type="submit"/>
          </form>
          <br /><br />
          <div>
              送信ボタンを押すとメール宛に検証リンクを送りますので、クリックしてからログインしてください。
          </div>
        </Paper>
      </div>
    );
  }
}
```


　次にnew CognitoUserPool()でuserPoolを作成します。そしてuserPool.signUp()でユーザを登録します。省略しましたが、ユーザプールの設定でメールアドレスの検証を必要にしましたので、userPool.signUp()が呼ばれるとメールが送られ検証リンクをクリックするようにとのメッセージが送られます。クリックするとメールアドレスが検証済みとなって、ログインが可能となります。


# ３．パスワード再発行
　パスワードを忘れた場合に再発行する画面です。まずメールアドレスだけを入力して送信すると検証コードが送られると同時に、入力フォームが自動的に切り替わります。そのフォームで検証コードと新パスワードを入力するとパスワードが新しいものに置き換わります。

```js:src/views/RecoverView.js
import {Config, CognitoIdentityCredentials} from "aws-sdk";
import {
  CognitoUserPool,
  CognitoUserAttribute,
  CognitoUser,
  AuthenticationDetails
} from "amazon-cognito-identity-js";
import React from 'react';
import { userIdFunc, handleErrorFunc } from '../App';
import TextField from 'material-ui/TextField';
import Paper from 'material-ui/Paper';
import RaisedButton from 'material-ui/RaisedButton';
import appConfig from '../appConfig';

const userPool = new CognitoUserPool({
  UserPoolId: appConfig.UserPoolId,
  ClientId: appConfig.ClientId,
});

export default class RecoverView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      email: "",
      sent: false,
      code: "",
      password: ""
    };
  }

  handleEmailChange(e) {
    this.setState({email: e.target.value});
  }
  handleCodeChange(e) {
    this.setState({code: e.target.value});
  }
  handlePasswordChange(e) {
    this.setState({password: e.target.value});
  }
  handleRequestClose = () => {
    this.setState({ sent: false });
  };
  handlePasswordSubmit = () => {
      const _self=this;
      const email = this.state.email.trim();
      var userData = {
          Username : email,
          Pool : userPool
      };
      var cognitoUser = new CognitoUser(userData);
      cognitoUser.confirmPassword(this.state.code, this.state.password,
         {
            onFailure(err) {
                console.log("### ##error="+err)
            },
            onSuccess() {
                console.log("### ##success")
            }
         }
      );
      _self.props.history.push({pathname: '/login'});
  }

  handleSubmit(e) {
    e.preventDefault();
    const _self=this;
    const email = this.state.email.trim();
    var userData = {
          Username : email,
          Pool : userPool
    };
    var cognitoUser = new CognitoUser(userData);

    cognitoUser.forgotPassword({
        onSuccess: function (data) {
          // successfully initiated reset password request
	      console.log('CodeDeliveryData from forgotPassword: ' + data);
        },
        onFailure: function(err) {
            alert(err);
        },
        //Optional automatic callback
        inputVerificationCode: function(data) {
            _self.setState({ sent: true });
        }
    });
  }


  render () {
    const style = {
      width: '80%',
      margin: 50,
      padding: 50,
      display: 'inline-block',
    };

    let myform = "";
    if(!this.state.sent) {
        myform = (
          <form onSubmit={this.handleSubmit.bind(this)}>
          <h2 style={{textAlign: 'left'}}>パスワード再発行</h2>
          <p style={{textAlign: 'left'}}>登録済みメールアドレスを入力して送信ボタンを押すと、検証コードをお送りします。</p>
            <TextField fullWidth={true}
                 value={this.state.email}
                 hintText="Email"
                 onChange={this.handleEmailChange.bind(this)}/><br />
            <input type="submit"/>
          </form>
        )
    } else {
        myform = (
          <form onSubmit={this.handlePasswordSubmit}>
            <p>検証コードをメールで送りました。新パスワードを設定してください。</p>
            <TextField  fullWidth={true}
                value={this.state.code}
                hintText="検証コード"
                onChange={this.handleCodeChange.bind(this)}/>
            <TextField  fullWidth={true}
                value={this.state.password}
                hintText="新パスワード"
                onChange={this.handlePasswordChange.bind(this)}/>
            <input type="submit"/>
          </form>
        )
    }

    return (
      <div>
        <Paper style={style} zDepth={1}>
          {myform}
        </Paper>
      </div>
    );
  }
}
```

　ユーザ登録と同じですが、最初にメールアドレスを入力して送信を押すとcognitoUser.forgotPassword()で検証コードを送ります。同時にフォームが切り替わりますので、検証コードと新パスワードを入力するとcognitoUser.confirmPassword()でパスワードを上書きします。


# ４．ログイン

　メールアドレスとパスワードを入力してログインします。ログインが成功すると自動的にアプリのメニューが変更され、管理メニューから記事が投稿できるようになります。

```js:src/views/LoginView.js
import AWS from "aws-sdk";
import {
  CognitoUserPool,
  CognitoUserAttribute,
  CognitoUser,
  AuthenticationDetails
} from "amazon-cognito-identity-js";
import React from 'react';
import { userIdFunc, handleErrorFunc } from '../App';

import TextField from 'material-ui/TextField';
import Paper from 'material-ui/Paper';
import appConfig from '../appConfig';

const userPool = new CognitoUserPool({
  UserPoolId: appConfig.UserPoolId,
  ClientId: appConfig.ClientId,
});

export default class LoginView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      email: "",
      password: ""
    };
  }

  handleEmailChange(e) {
    this.setState({email: e.target.value});
  }
  handlePasswordChange(e) {
    this.setState({password: e.target.value});
  }

  handleSubmit(e) {
    e.preventDefault();
    const email = this.state.email.trim();
    const password = this.state.password.trim();
    const authenticationData = {
        Username : email,
        Password : password,
    };

    var authenticationDetails = new AuthenticationDetails(authenticationData);
    const _self = this;
    const userData = {
        Username : email,
        Pool : userPool
    };
    const cognitoUser = new CognitoUser(userData);

    cognitoUser.authenticateUser(authenticationDetails, {
        onSuccess: function (result) {
            //console.log('access token + ' + result.getAccessToken().getJwtToken());

            AWS.config.region = appConfig.region;
            AWS.config.credentials = new AWS.CognitoIdentityCredentials({
                IdentityPoolId: appConfig.IdentityPoolId,
                Logins : {
                    // Change the key below according to the specific region your user pool is in.
                    'cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xn9ihTu0b' : result.getIdToken().getJwtToken()
                }
            });
//================================================
//            AWS.config.credentials.refresh(function(){
            AWS.config.credentials.get(function(){
//================================================
              //console.log("##############"+AWS.config.credentials)
              var identityId = AWS.config.credentials.identityId;
              _self.setState({identityId: identityId});
              var username = "";
              cognitoUser.getUserAttributes((err, result) => {
                for (var i = 0; i < result.length; i++) {
                    //console.log('attribute ' + result[i].getName() + ' has value ' + result[i].getValue());
                    if( result[i].getName() === "name") {
                        username = result[i].getValue();
                        //console.log("### username"+username);
                        localStorage.setItem('username', username);
                        break;
                    }
                }
              });
             localStorage.setItem('email', email);
             _self.props.history.push({pathname: '/'});
//================================================
            })
//================================================
        },
        onFailure: function(err) {
            alert(err);
        },
    });
  }

  render () {
    const style = {
      width: '80%',
      margin: 50,
      padding: 50,
      display: 'inline-block',
    };
    return (
      <div>
        <Paper style={style} zDepth={1}>
          <h2 style={{textAlign: 'left'}}>ログイン</h2>
          <form onSubmit={this.handleSubmit.bind(this)}>
            <TextField fullWidth={true}
                 value={this.state.email}
                 hintText="Email"
                 onChange={this.handleEmailChange.bind(this)}/><br />
            <TextField fullWidth={true}
                 value={this.state.password}
                 hintText="パスワード"
                 onChange={this.handlePasswordChange.bind(this)}/><br />
            <input type="submit"/>
          </form>
        </Paper>
      </div>
    );
  }
}
```

　入力されたメールアドレスとパスワードをcognitoUser.authenticateUser()で認証します。認証が成功すると以下のコードでcredentialsの変更を行います。これでログインユーザとしての権限と持つことになります。「２．非ログインユーザ」で示したコードと比べてください。

```js
            AWS.config.credentials = new AWS.CognitoIdentityCredentials({
                IdentityPoolId: appConfig.IdentityPoolId,
                Logins : {
                    // Change the key below according to the specific region your user pool is in.
                    'cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xn9ihTu0b' : result.getIdToken().getJwtToken()
                }
            });
```

~~　但しcredentialsの変更はAWS.config.credentials.refresh()で初めて実行され有効になりますので注意してください。（知らなかったので時間をだいぶ無駄にしました）。このタイミングで非ログインユーザからログインユーザへと変更されます。~~

ここで、**AWS.config.credentials.get()を使うことでAWS.config.credentials.identityIdの値として、UserPoolのユーザに結びついたユニークな値が取得できます。**　またここのコードはAWS.config.credentials.refresh()を呼んでも動作しましたが下記のドキュメントに従います。(2018/12/08)
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/getting-credentials.html



大切なことなので繰り返しますが、**ログイン状態のときのAWS.config.credentials.identityIdは、userPoolのユーザに結びついたユニークな値が割り振られ、ユーザIDとして使えます。ログイン前やログアウト後では、ゲストユーザとしてテンポラリな値が割り振られます。**ログイン／非ログイン時にはそれぞれのRoleを持ちますが、詳細は「[AWS+Reactアプリ作成入門(IAM Role編）](https://qiita.com/sand/items/a07a08f7c1ccc2e8b967 "AWS+Reactアプリ作成入門(IAM Role編）")」を参照してください。(2018/12/08)

更に、ドキュメントを読んでも、あまり明確に描かれていないことがあります。それはテンポラリなidentityIdがどんどん増えていくことです。AWSのコンソールで確認すると結構な増え方をします。結構気になりますが、[**「Amazon Cognito における制限」**](
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/limits.html)を読むと、**「ID プールあたりの ID の最大数は無制限」**らしいので、気にする必要はないのかもしれません。知っている方がいたら教えてほしい。(2018/12/08)

　ログインが成功したらemailとusernameをlocalStorageに保存しておきます。これはログアウト時またはAWS.config.credentials.expiredがtrueになった時に削除します。

```js
            AWS.config.credentials.refresh(function(){
              var identityId = AWS.config.credentials.identityId;
              _self.setState({identityId: identityId});

---
                        localStorage.setItem('username', username);
---
             localStorage.setItem('email', email);
             _self.props.history.push({pathname: '/'});
            })
```

　ちなみに私はgetAccessToken()とgetIdToken()を間違えて数日を無にしました。苦い思い出です。


# ５．ログアウト

　ログインに成功するとアプリメニューからログインメニューが消え、ログアウトメニューが現れます。ログアウトボタンで非ログインユーザに戻ることができます。

```js:src/views/LogoutView.js
"use strict";
import AWS from "aws-sdk";
import React from 'react';
import { Paper } from 'material-ui';
import { userIdFunc, handleErrorFunc } from '../App';
import appConfig from '../appConfig';

class LogoutView extends React.Component {
  constructor(props) {
    super(props);
  }
  componentDidMount() {
      localStorage.removeItem('email');
      localStorage.removeItem('username');

      // console.log("##### before expired="+AWS.config.credentials.expired); // false

      AWS.config.region = appConfig.region;
      AWS.config.credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId: appConfig.IdentityPoolId
      });
      AWS.config.credentials.refresh(function(){
      })

      setTimeout( ()=>this.props.history.push({pathname: '/'}) ,3000) ;
  }

  render () {
    return (
      <div style={{width: 400, margin: "auto"}}>
        <Paper zDepth={3} style={{padding: 32, margin: 32}}>
          ログアウト成功！
        </Paper>
      </div>
    );
  }
}
export default LogoutView;
```

　ちょっとドキュメントに明記されていないので自信が無いのですが、以下のコードでログインユーザから非ログインユーザに戻るようです。localStorageからemailとusernameも削除します。

```
      AWS.config.credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId: appConfig.IdentityPoolId
      });
      AWS.config.credentials.refresh(function(){
      })
```

# ６．パスワード変更

　最後です。ログインユーザとしてパスワードを変更します。現行のパスワードと新パスワードを入力して上書きします。

```js:src/views/ChangePasswordView.js
import AWS from "aws-sdk";
import {
  CognitoUserPool,
  CognitoUserAttribute,
  CognitoUser,
  AuthenticationDetails
} from "amazon-cognito-identity-js";
import React from 'react';
import { userIdFunc, handleErrorFunc } from '../App';
import TextField from 'material-ui/TextField';
import Paper from 'material-ui/Paper';
import appConfig from '../appConfig';

export default class LoginView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      email: "",
      oldpassword: "",
      newpassword1: "",
      newpassword2: ""
    };
  }

  componentWillMount() {
    const email = localStorage.getItem('email');
    this.setState({email: email});
  }

  handleOldPasswordChange(e) {
    this.setState({oldpassword: e.target.value});
  }
  handleNewPassword1Change(e) {
    this.setState({newpassword1: e.target.value});
  }
  handleNewPassword2Change(e) {
    this.setState({newpassword2: e.target.value});
  }

  handleSubmit(e) {
    e.preventDefault();
    const _self = this;
    const email = this.state.email;
    if( !email ) {
        handleErrorFunc('エラー：ログインしていません');
        return;
    }
    const oldpassword = this.state.oldpassword.trim();
    const newpassword1 = this.state.newpassword1.trim();
    const newpassword2 = this.state.newpassword2.trim();
    if(newpassword1 !== newpassword2) {
        handleErrorFunc('エラー：確認用パスワードが一致しません。');
        return;
    }

    const authenticationData = {
        Username : email,
        Password : oldpassword,
    };
    var authenticationDetails = new AuthenticationDetails(authenticationData);

    const userPool = new CognitoUserPool({
      UserPoolId: appConfig.UserPoolId,
      ClientId: appConfig.ClientId,
    });
    const userData = {
        Username : email,
        Pool : userPool
    };
    var cognitoUser = new CognitoUser(userData);
    cognitoUser.authenticateUser(authenticationDetails, {
        onSuccess: function (result1) {
            cognitoUser.changePassword(oldpassword, newpassword1, function(err, result) {
                if (err) {
                    alert(err);
                    handleErrorFunc('エラー：パスワード変更に失敗しました: '+err);
                    return;
                }
                _self.setState({oldpassword: ""});
                _self.setState({newpassword1: ""});
                _self.setState({newpassword2: ""});
                //console.log('call result: ' + result);
            });
        },
        onFailure: function(err) {
            alert(err);
        },
    });
  }

  render () {
    const style = {
      width: '80%',
      margin: 50,
      padding: 50,
      display: 'inline-block',
    };
    return (
      <div>
        <Paper style={style} zDepth={1}>
          <h2 style={{textAlign: 'left'}}>パスワード変更</h2>
          <form onSubmit={this.handleSubmit.bind(this)}>
            <TextField fullWidth={true}
                 value={this.state.oldpassword}
                 hintText="現在のパスワード"
                 onChange={this.handleOldPasswordChange.bind(this)}/><br />
            <TextField fullWidth={true}
                 value={this.state.newpassword1}
                 hintText="新パスワード"
                 onChange={this.handleNewPassword1Change.bind(this)}/><br />
            <TextField fullWidth={true}
                 value={this.state.newpassword2}
                 hintText="新パスワード（確認用）"
                 onChange={this.handleNewPassword2Change.bind(this)}/><br />
            <input type="submit"/>
          </form>
        </Paper>
      </div>
    );
  }
}
```

　 パスワードの変更はcognitoUser.changePassword()で行いますが、その前にcognitoUser.authenticateUser()を行っておく必要があります。これはログイン時に行っているので、その時のcognitoUserを親Componentのstateか、Redux storeに保存しておけば、ここでのauthenticateUser()は不要になるのだろうか？　今回はこの方がシンプルな気がして試していません。

　今回はこの辺で終わります。今後もこのアプリの全体を書いていくつもりです。非ログインユーザ／ログインユーザとしてDynamoDBやS3、lambda関数の利用を書きます。特にDynamoDBはMongnDBなどと比べると癖が強いので。今回素通りしたCognitoのRoleについてもいずれ示していきたいと思います。