Cognitoユーザー認証をAmplifyとReactを使って実装する。(その4)

実際に作成したApplicationの動作を確認していきます。
npm start
すると、DefaultのBrowserが立ち上がり、「Sign in to your account」画面が表示されます。

最初はCognitoに一つもAccountが設定されていないので、下側にある「Create Account」をクリックして、「Create a new account」画面に移動し、UsernameとPasswordを入力、「CREATE ACCOUNT」をクリックして、新しいAccountを作成します。

「Your account is not granted」と表示されます。これは、Eメールでの自動認証の設定をしていないためで、Management Consoleから認証します。

Management ConsoleからCognitoサービスを開き、「ユーザープールの管理」へ移動します。「ユーザープール」では、作成したユーザープールをクリックし、画面左側の「ユーザーとグループ」タブをクリックします。
先ほど作成したユーザーが、「UNCONFIRMED」の状態で表示されます。ユーザー名をクリックして、詳細を表示し、「ユーザーの確認」をクリックします。「アカウントのステータス」が、「Enabled/CONFIRMED」になった事を確認して、画面左側の「ユーザーとグループ」をクリックし、ユーザーの一覧を表示させます。

Browserに戻って、「Goto Signin」をクリックして、「Sign in to your account」画面に戻り、先ほど作成したAccountのUsernameとPasswordを入力し、「SIGN IN」をクリックします。

お馴染みのReactの画面が表示されます。
右上の「SIGN OUT」をクリックしてSign Outすると、 「Sign in to your account」画面に戻り ます。

これで、AWS Cogintoを使ったServerlessのユーザー管理が出来ることが分かりました。
Client側のJava ScriptからCognitoのSign inの状態が分かる、という所まで理解できたと思います。

最後に、このApplicationをS3にDeployします。
cd export
npm run build
aws s3 sync --exact-timestamps --delete --acl public-readdirname bucketname

S3にUploadが完了したら、 Web BrowserからAccessしてみましょう。Localで実行した時と同じ画面になり、動作も同じになります。

Serverlessでユーザー管理を実現ができることが分かりました。

Cognitoユーザー認証をAmplifyとReactを使って実装する。(その3)

Client側のAmplifyをInstallします。環境は前回「Reactを使ってStaticなWeb Hostingをする。」で使用した環境に追加します。
npm install --save aws-amplify aws-amplify-react

Client側のApplicationを作って行きます。ReactのWelcome画面にCognitoユーザー認証を追加します。

初めに読み込まれる、index.jsにAWS Amplifyの設定を入れて置く”services”というfileをimportする行を追加します。
import { configureAmplify } from './services'
そのあとで、Amlifyをconfigureするために、
configureAmplify();
を呼び出します。ReactDom.renderに渡すelementを<App />から<AppWithAuth />に変更し、ユーザー認証を追加した描画処理に変更します。
ReactDOM.render(<AppWithAuth />, document.getElementById('root'));

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import AppWithAuth from './AppWithAuth';
import * as serviceWorker from './serviceWorker';

//Import Amplify Configure function
import { configureAmplify } from './services'

configureAmplify();

ReactDOM.render(<AppWithAuth />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

index.jsからimportされる”services”を準備します。”services”では、Amplifyの設定をしますが、実際の情報は環境変数に設定しておき、起動時に”.env”から読み込むようにしておきます。このようにすることで、環境が変わっても再buildすることなく、環境を変更することができるようになります。

import Amplify from '@aws-amplify/core';

export function configureAmplify() {
  Amplify.configure(
  {
    Auth: {
      region: process.env.REACT_APP_region,
      userPoolId: process.env.REACT_APP_userPoolId,
      userPoolWebClientId: process.env.REACT_APP_userPoolWebClientId,
      identityPoolId: process.env.REACT_APP_identityPoolId,
   }
 } );
}

“.env”で4つの情報を設定し、projectのroot directoryに置きます。
REACT_APP_region=ap-northeast-1
REACT_APP_userPoolId=ap-northeast-1yourPoolID
REACT_APP_userPoolWebClientId=yourWebClientId
REACT_APP_identityPoolId=ap-northeast-1:yourIdentityPoolId
これらの情報は、Cognito User Poolを作成した時に生成されたUser ID、App Client IDと、Cognito Identity Poolを作成した時に生成されたIdentity Pool IDを設定します。これらは、Cognitoに接続するための情報ですが、URLのような物で隠す必要はありません。ボットによる攻撃を避けるためには、Cognito User Poolの作成の際に、Client Deviceの認証を追加するなど検討します。

次に、 ReactDom.renderから最初に描画されるelement、”AppWithAuth.js”を作成します。今回はEメールによる認証を行わないため、Login画面を簡素化しました。単純にusernameとpasswordだけを入力できるようにしています。

import React from 'react';
import { Authenticator, ConfirmSignIn, RequireNewPassword, ConfirmSignUp, VerifyContact, ForgotPassword, TOTPSetup } from 'aws-amplify-react';
import App from "./App";
import MyVerifyContact from "./MyVerifyContact"
import MyConfirmSignUp from "./MyConfirmSignUp"

const signUpConfig = {
  hideAllDefaults: true,
  signUpFields: [
    {
      label: 'Username',
      key: 'username',
      required: true,
      placeholder: 'Username',
      displayOrder: 1
    },
    {
      label: 'Password',
      key: 'password',
      required: true,
      placeholder: 'Password',
      type: 'password',
      displayOrder: 2
    }
  ]
}

function AppWithAuth() {

  function handleAuthStateChange(state, data) {
    if (state === 'confirmSignIn') {
        /* Do something when the user has signed-in */
    }
  }

  return (
    <Authenticator signUpConfig={signUpConfig} hide={[ConfirmSignIn, RequireNewPassword, VerifyContact, ConfirmSignUp, ForgotPassword, TOTPSetup]} onStateChange={handleAuthStateChange}>
      <MyVerifyContact override={VerifyContact} />
      <MyConfirmSignUp override={ConfirmSignUp} />
      <App />
    </Authenticator>
  )
}

export default AppWithAuth;

Login画面を簡素化するために、Amplifyが提供している”VerifyContact”、”ConfirmSignUp”をoverrideしてCustomizeしています。Amplifyは手っ取り早くApplicationを作成できるのですが、こういったCustomizeをするのが簡単ではありません。

import { AuthPiece } from 'aws-amplify-react';

class MyVerifyContact extends AuthPiece {
  constructor(props) {
    super(props);
    this._validAuthStates = ["verifyContact"];
    this.state = {}
  }

  static getDerivedStateFromProps(props, state) {
    if(props.authState === "verifyContact") {
      props.onStateChange('signedIn');
      return props;
    } else {
      return null;
    }
  }

  showComponent(theme) {
    return null;
  }

}

export default MyVerifyContact;
import React from 'react';

import { I18n } from 'aws-amplify';
import { AuthPiece } from 'aws-amplify-react';

import {
  FormSection,
  SectionHeader,
  SectionBody,
  SectionFooter,
  Button
} from 'aws-amplify-react';

class MyConfirmSignUp extends AuthPiece {
  constructor(props) {
    super(props);
    this._validAuthStates = ["confirmSignUp"];
    this.gotoSignIn = this.gotoSignIn.bind(this);
  }

  gotoSignIn() {
    super.changeState('signIn');
  }

  showComponent(theme) {
    return (
      <FormSection theme={theme}>
        <SectionHeader theme={theme}>{I18n.get('Please contact your administrator to grant your account.')}</SectionHeader>
        <SectionBody theme={theme}>
        </SectionBody>
        <SectionFooter theme={theme}>
          <Button onClick={this.gotoSignIn} theme={theme}>
            {I18n.get('Goto SignIn')}
          </Button>
        </SectionFooter>
      </FormSection>
    );

  }

}

export default MyConfirmSignUp;

最後に、”App.js”を編集して、Loginの状態を表示するようにします。

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App(props) {

  if(props.authState === 'signedIn') {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  } else if(props.authState === 'confirmSignUp') {
    return (
      <div className="Your account is not granted">
        <header className="App-header">
          Your account is not granted.
        </header>
      </div>
    );
  } else if(props.authState === 'signIn') {
    return (
      <div className="Welcome to the world">
        <header className="App-header">
          Welcome to the world.
        </header>
      </div>
    );
  } else {
    return (
      <div className="Login failed">
        <header className="App-header">
          Login failed.
        </header>
      </div>
    );
  }
}

export default App;

今回はLocal環境からnpm startしてみます。

AmplifyのLogin画面が表示されます。

次回は、ユーザー認証の方法について説明していきます。

Cognitoユーザー認証をAmplifyとReactを使って実装する。(その2)

次にFederated Identity Poolを作成します。Management ConsoleからCognitoを開き、「IDプールの管理」をクリックします。

「新しいIDプールの作成」で、「IDプール名」を設定します。「認証プロバイダー」の▶をクリックして開き、「Cognito」タブを設定します。Cognito User Poolで作成した、「ユーザープールID」と「アプリクライアントID」を設定し、「プールの作成」をクリックします。

「Your Cognito identities require access to your resources」では、「詳細を表示」の▶をクリックし開きます。Cognito Federated IdentityにRoleを設定する必要があり、ここで新しく作成されるRoleが表示されます。
ここの画面は英語が混ざっていたり、既存のRoleを設定することが出来なかったり、デフォルト以外のRoleを設定できなかったりと、今一な作りです。
「許可」をクリックします。
既にRoleがあったり、Roleを作成する権限がなかったりすると、エラーになりますが、Cognito Federated Identityは作成されます。エラーになった場合は、Dash Boardに戻って内容を確認し、必要があれば修正します。
「Amazon Cognitoでの作業開始」では、「ダッシュボードに移動」をクリックして「ダッシュボード」に移動し、右上の「IDプールの編集」をクリックします。

「IDプールの編集」では、作成した「IDプールのID」を確認します。このIDは、Amplifyの設定で使用します。Roleが正しく設定されているか確認します。Management ConsoleからIAMを開き、設定されているRoleのTrust Relationshipsが正しく設定されているか確認します。

これで、Federated Identity Poolの作成は完了です。

Cognitoユーザー認証をAmplifyとReactを使って実装する。(その1)

Web Serviceを実現する上で、Serviceを運営する側のUser管理はIAM Userで行います。不特定多数のUserにServiceを提供する場合は、特段のユーザー認証は必要ありませんが、不特定多数のUserからAccessを無制限に受け付けてしまうと、意図しないFileがUploadされたり、意図しない情報漏洩が起きたり、と問題となることが考えられます。一般的に不特定多数のUserからのAccessはRead Onlyとし、認証された特定多数のUserに対し、FileのUploadや、特定の情報を開示するようにすることが必要になります。

Web Serviceを利用するUserに対するUser管理は、Data Base(DB)を使って管理するのが一般的です。Serviceを24時間提供しようとすると、24時間User管理のDBを稼働させておく必要があります。AWSには、CognitoというUser管理Serviceがあり、これを利用すれば自前でUser管理用のDBを稼働させておく必要がなくなります。また、 AWSにはAmplifyというOpen SourceのLibraryがあり、Client側の実装を簡単に実現できてしまいます。まだまだ発展途上の感はありますが、これを使ってユーザー認証を実装してCognitoの使い方を把握していきます。

AWSのWeb PageのInstructionに従って
amplify configure
を実行すると、Server側も設定してくれます。ただ、これからWeb Serviceを運用する側からすると、裏で何が起こっているのか分からないのは気持ちが悪く、Server側の設定は全て把握しておきたい所です。今回は、Server側の設定は手作業で行います。最終的にはScriptにしてDeployの自動化を目指すことになりますが、今はAWSに必要な設定を理解することに主眼を置きます。
今回は、特定のユーザーにのみServiceを公開する、という前提で、Eメールでの認証をせず、管理者が手動で認証する方式にします。

それでは、Cognito User Poolを作成していきます。Management ConsoleからCognitoを開きます。「ユーザープールの管理」をクリックして、ユーザープール作成画面に移ります。「ユーザープールを作成する」をクリックして、ユーザープールを作成する画面、名前タブに移ります。

「名前」タブでは、「プール名」を入力して、「ステップに従って設定する」をクリックします。

「属性」タブに移ったら、「エンドユーザーをどのようにサインインさせますか」では、「ユーザー名」を選択、「どの標準属性が必要ですか?」では、全ての項目のチェックを外します。「カスタム属性を追加しますか」では、「カスタム属性の追加」をクリックして、カスタム属性を追加しておきます。今回は「group」という属性を追加し、変更可能のチェックを外しました。「次のステップ」をクリックします。

「ポリシー」タブでは、「パスワードの強度はどれくらいを要求しますか?」は、希望のパスワード強度を設定します。今回は「特殊文字を要求する」のチェックを外しました。「ユーザーに自己サインアップを許可しますか?」は、「ユーザーに自己サインアップを許可する」のチェックのままにします。「有効期限(日数)」は「7」のままにして、「次のステップ」をクリックします。

「MFAそして確認」タブ、「メッセージのカスタマイズ」タブは、Eメールを使わないので内容は変更せず「次のステップ」をクリックしていきます。

「タグ」タブでは、タグを設定して、 「次のステップ」をクリックします。

「デバイス」タブは変更せず、「次のステップ」をクリックします。

「アプリクライアント」タブでは、アプリクライアントの設定を行います。このアプリクライアントの情報をAmplifyで使用して、Cognitoのユーザー認証を行うことになります。「アプリクライアント名」を設定して、「クライアントシークレットを作成」のチェックを外し、あとは変更せず、「アプリクライアントの作成」をクリックします。 「このユーザープールへのアクセス権限があるアプリクライアントはどれですか?」では、内容を確認して「次のステップ」をクリックします。

「トリガー」タブは変更せず、「次のステップ」をクリックします。
「確認」タブで内容を確認して、「プールの作成」をクリックします。

「ユーザープールは正常に作成されました。」と表示されるので、その下の「プールID」を確認します。 このIDは、Cognito Federated Identityの作成時と、Amplifyの設定で使用します。
左側の「アプリクライアント」をクリックして、「アプリクライアントID」を確認します。「詳細を表示」をクリックして詳細も確認します。 アプリクライアントIDは、Cognito Federated Identityの作成時と、Amplifyの設定で使用します。「アプリクライアントのシークレット」 が「シークレットキーなし」になっていることを確認します。
最後に左側の「全般設定」の下の「ユーザーとグループ」をクリックして、作成されたCognito User Poolが空であることを確認しておきます。

以上で、Cognito User Poolの作成が完了しました。

Reactを使ってStaticなWeb Hostingをする。(その2)

それでは、前回作成したReactのSample PageをAWS S3にDeployしましょう。
AWS S3にDeployする際、AWSのManagement ConsoleからWeb Pageを構成するJava Script Fileを一つ一つUploadするのは現実的ではありません。Command LineからCommand一発で、直接Uploadしたいですよね。そこで、AWSのCLI(Command Line Interface)を使ってUploadすることにします。

まず、AWS上にS3へのUpload権限をもつIAM (Identity and Access Management) Userを作成します。AWSでは、このような権限管理が徹底していて、強靭なSystemを構成することができます。その反面、設定が煩雑になり、難解で取っつきにくい印象を持たれているように感じます。それはさておき、AWS Management ConsoleからIAM Serviceを呼び出します。

IAMの「ダッシュボード」から、「ユーザ」をクリック、「ユーザを追加」をクリックしてユーザの追加を行います。ユーザ名を「S3UploadUser」として、アクセスの種類は「 プログラムによるアクセス 」をチェックします。「 AWS マネジメントコンソールへのアクセス 」は、このユーザでManagement ConsoleにLoginすることは無いので、チェックしません。

「アクセス許可の設定」では、「既存のポリシーを直接アタッチ」をクリックし、「ポリシーのフィルタ」に「S3」を入力し、S3関連のPolicyを表示させます。この中から、「AmazonS3FullAccess」をチェックして、 「S3UploadUser」 に権限を与えます。

後々、管理しやすいようにタグを付けておきましょう。

内容を確認して、「ユーザの作成」をクリックしてユーザを作成します。ユーザが作成されたら、必ず「.csvのダウンロード」をクリックして、.csvをダウンロードして保存してください。AWS CLIからAWSへのアクセスする際に、.csvに保存されているアクセスキーIDとシークレットアクセスキーを使って認証を行います。このキーペアーが外部に漏れると、外部からAWSへアクセスし悪用される可能性があるため、外部に漏れないように管理してください。

作成したユーザを確認しましょう。作成したユーザ名をクリックすると、「概要」の「アクセス権限」タブに許可されている権限が表示されます。「認証情報」タブをクリックして、「サインイン認証情報」の「コンソールパスワード」が無効になっていることを確認します。また、「アクセスキー」に「アクセスキーID」があることを確認します。このアクセスキーIDに対応するシークレットアクセスキーは、先ほど保存した.csvにあります。もし紛失した場合は、紛失したアクセスキーIDを無効化し、再度この画面から「アクセスキーの作成」を行うことで再作成することができます。

次はAWS CLIのInstallです。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-windows.html
を参考にInstallします。今回は、「 Windows (64 ビット) 用 の AWS CLI MSI インストーラのダウンロード 」を使います。

Downloadが完了したら、AWSCLI64PY3.msiをダブルクリックしてInstallしていきます。

Installが完了したら、AWS CLIの設定を行います。
aws configure

ここで、先ほど保存したアクセスキーIDとシークレットアクセスキーを入力します。Regionは、ap-northeast-1(東京)を指定、Output formatはjsonを指定します。

これで環境設定は完了です。

次にReactの設定を変更します。 前回exportというディレクトリにReactのApplicationを作成しました。exportの直下にpackage.jsonファイルがあります。AWS S3にUploadしてWeb Applicationを構成する場合、AWS S3のURLを指定してBuildする必要があるため、”homepage”というエントリを追加します。

{
  "name": "export",
  "version": "0.1.0",
  "private": true,
  "homepage" : "https://s3-ap-northeast-1.amazonaws.com/export.hacya.com ",
  "dependencies": {
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-scripts": "3.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

Web ApplicationをBuildします。結果は、buildディレクトリに作成されます。
cd export
npm run build

作成したWeb ApplicationをAWS S3にDeployします。ここで、先にInstallしたAWS CLIを使います。AWS S3 Syncを使って、S3にCodeをUploadします。
aws s3 sync --exact-timestamps --delete --acl public-read dirname bucketname

Uploadが完了したら、index.htmlの時と同じようにWeb BrowserからAccessしてみましょう。ReactをLocalで実行した時と同じ画面になります。

これで、少し複雑なStatic Web ApplicationをAWS S3を使って公開することができることになります。