【Amplify】Cognitoの認証フォームのサインアップを無効にする方法

AWS公式のAmplifyチュートリアルで構築したReactアプリの中で、Amplify UIを使ったCognitoの認証フォームのサインアップ機能を無効にする方法を記載します。クライアントにはログインフォームだけ表示させて、ユーザ登録は、管理者がGUIからできるようにしたいのです。

システムは以下のチュートリアルで用意しました。
> Amplify Dev Cnter > Docs （チュートリアル）
https://docs.amplify.aws/start/q/integration/react/

チュートリアルをやってみたの記事はこちらを参考ください。
> AWS Amplifyでメモアプリを構築
https://qiita.com/ayumu__/items/5a4fc739146f04e88966


## お品書き
- 概要
- サインアップ機能の無効化
- むすび

## 概要
チュートリアルに存在するsrc/App.jsを改変しているため、そちらのコードと比較して無効化の方法を示します。
チュートリアルでは、withAuthenticatorコンポーネントというものでラップすることでサインイン画面等を表示していましたが、withAuthenticatorだとサインアップボタンを非表示にする方法を、私は見つけられませんでした（少ししか探してませんが）。
そこで、AuthenticatorというUIコンポーネントを使って、これを実現してみます。具体的にはaws-amplify-reactのAuth モジュールに対して、サインアップフォーム部分を無くすためのオプションを付与します。
ちなみに、Amplify UIの公式ドキュメントはこれです。
> Amplify Dev Center > Authenticator
https://ui.docs.amplify.aws/react/connected-components/authenticator

こちらの記事も参考にしました。
> Amplify UIを使ってReactアプリにAmazon Cognitoの認証フォームを実装する
https://dev.classmethod.jp/articles/implement-amazon-cognito-authentication-in-react-apps-using-the-amplify-ui/

## サインアップ機能の無効化

サインアップ機能の無効化を実現するコードは以下です。チュートリアルと比較した更新箇所をdiffで表現してます。
(赤:チュートリアル、緑:変更後）
コメントアウトは適宜参考にしてください。
```diff_javascript:src/App.js
/* src/App.js */
import React, { useEffect, useState } from 'react'
import { Amplify, API, graphqlOperation } from 'aws-amplify'
import { createTodo } from './graphql/mutations'
import { listTodos } from './graphql/queries'

- import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react';
+ import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';

import awsExports from "./aws-exports";
Amplify.configure(awsExports);

const initialState = { name: '', description: '' }

function App({ signOut, user }) { 
  const [formState, setFormState] = useState(initialState)
  const [todos, setTodos] = useState([])

  useEffect(() => {
    fetchTodos()
  }, [])

  function setInput(key, value) {
    setFormState({ ...formState, [key]: value })
  }

  async function fetchTodos() {
    try {
      const todoData = await API.graphql(graphqlOperation(listTodos))
      const todos = todoData.data.listTodos.items
      setTodos(todos)
    } catch (err) { console.log('error fetching todos') }
  }

  async function addTodo() {
    try {
      if (!formState.name || !formState.description) return
      const todo = { ...formState }
      setTodos([...todos, todo])
      setFormState(initialState)
      await API.graphql(graphqlOperation(createTodo, {input: todo}))
    } catch (err) {
      console.log('error creating todo:', err)
    }
  }
  

  return (
    /*ユーザー自身によるサインアップをさせない場合は、hideSignUpをtrueで指定する*/
    /*UserIDの入力欄のプレースホルダーを変えたい場合は、loginMechanismsを指定する*/
    // <Authenticator hideSignUp={true} loginMechanisms={['email']}>
+   <Authenticator hideSignUp={true}>
    <div style={styles.container}>
-   <Heading level={1}>Hello {user.username}</Heading>
-   <Button onClick={signOut} style={styles.button}>Sign out</Button>
    {/* アロー関数の前にAuhenticatorを入れないとsignOutボタンなくなる */}
+   <Authenticator>
+    {({ signOut, user }) => (
+        <main>
+          <h1>Hello {user.username}</h1>
+          <button onClick={signOut}>Sign out</button>
+        </main>
+      )}
+   </Authenticator>
      <h2>Amplify Todos</h2>
      <input
        onChange={event => setInput('name', event.target.value)}
        style={styles.input}
        value={formState.name}
        placeholder="Name"
      />
      <input
        onChange={event => setInput('description', event.target.value)}
        style={styles.input}
        value={formState.description}
        placeholder="Description"
      />
      <button style={styles.button} onClick={addTodo}>Create Todo</button>
      {
        todos.map((todo, index) => (
          <div key={todo.id ? todo.id : index} style={styles.todo}>
            <p style={styles.todoName}>{todo.name}</p>
            <p style={styles.todoDescription}>{todo.description}</p>
          </div>
        ))
      }
    </div>
+   </Authenticator>
  )
}

const styles = {
  container: { width: 400, margin: '0 auto', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: 20 },
  todo: {  marginBottom: 15 },
  input: { border: 'none', backgroundColor: '#ddd', marginBottom: 10, padding: 8, fontSize: 18 },
  todoName: { fontSize: 20, fontWeight: 'bold' },
  todoDescription: { marginBottom: 0 },
  button: { backgroundColor: 'black', color: 'white', outline: 'none', fontSize: 18, padding: '12px 0px' }
}

+ export default App
- export default withAuthenticator(App);
```
divの中に、\<Authenticator>\</Authenticator>が2セットでてきていますが、1セットの場合、うまく動きません。
出来上がったログイン画面はこちらです。サインアップフォームがないことがわかります。
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754596/ff018d06-af91-7f75-6401-9fe12fafcb9c.png)
ちなみに、チュートリアル通りに実装した場合、以下のようにサインイン、サインアップフォームがみえます。
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754596/46213f94-991b-bf3b-4d63-e425ed2f12c9.png)

ログイン後の画面はこのようになっています。Sign outボタンには特に装飾を施してませんが、クリックすることで、サインアウトされ認証の画面に戻ります。
![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754596/a0f1137d-f1ac-5c41-70c9-c5722f37199b.png)


## むすび
- 結構使う場面あるんじゃないでしょうか。
- さらに理解を深めて、認証フォームをカッコよく、また、要件を満たす実装を実現していきたいです。
それでは。