Amplify で GraphQL と Cognito 認証

# 概要
[はじめての Amplify デプロイまで](https://qiita.com/Tomoyuki_Mikami/items/a6f5b8803e58989c11c6) の続きです。
前回のプロジェクト作成時点で認証に Cognito を使うようにしたり、mock で必要な設定を docker に入れています。
Cognito によるユーザー認証と GraphQL との連携を実装します。

# ユーザー認証機能の追加
```
yarn add aws-amplify aws-amplify-react
```

App.tsx を以下のように変更します。
アカウント作成時に電話番号を不要にするために withAuthenticator の signUpConfig に hiddenDefaults を追加しています。

```
import React from 'react';
import Amplify from '@aws-amplify/core';
import awsmobile from './aws-exports';
import { withAuthenticator } from 'aws-amplify-react';
import '@aws-amplify/ui/dist/style.css';

Amplify.configure(awsmobile)

const App: React.FC = () => {
  return (
    <>
      <h1>タイトル</h1>
    </>
  )
}

//@ts-ignore
export default withAuthenticator(App, {
  signUpConfig: {
    hiddenDefaults: ['phone_number']
  }
});
```

※ import '@aws-amplify/ui/dist/style.css'; がないと認証画面のスタイルがない状態になるので必要でした
<img width="300" alt="スクリーンショット 2020-07-05 10.33.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/113a1e21-d6ab-f6af-9ee0-213af8482f24.png"><img width="300" alt="スクリーンショット 2020-07-05 10.34.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/ca0e0a15-496d-5d48-2885-6c51b2a0528a.png">

ログイン画面から Create account でアカウントを作成し、メールの認証をするとログインができます。
「タイトル」が表示されます。

# GraphQL API の作成
まずは自動生成された schema.graphql の TODO モデルを試してみます。

```graphql:schema.graphql
type Todo @model {
  id: ID!
  name: String!
  description: String
}
```

mock を利用することでローカルで検証ができます。

```
$ amplify mock api
```

http://localhost:20002 にアクセスすると以下のように表示されていると思います。
<img width="982" alt="スクリーンショット 2020-07-05 10.53.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/97b3555a-a0c2-b486-06c8-08c278506421.png">

TODO の作成
以下のように createTodo を実行しました。
<img width="1548" alt="スクリーンショット 2020-07-05 10.55.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/95bf2179-754d-caf4-d85d-bd83b241342b.png">

作成した TODO を取得してみます。
<img width="1572" alt="スクリーンショット 2020-07-05 10.57.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/b81667d8-9d70-451b-1c80-ceb6748cf1a7.png">

# react と GraphQL の連携
作成したデータを画面に表示してみます。

今回は docker でやっているので aws-exports.js の aws_appsync_graphqlEndpoint を
以下のように localhost に書き換えが必要です（ローカル開発中だけ）

```js:aws-exports.js
"aws_appsync_graphqlEndpoint": "http://localhost:20002/graphql",
```

App.tsx を以下のように変更します。
Todo に保存したデータの1つを表示します。

```tsx:App.tsx
import React, {useState} from 'react'
import Amplify from '@aws-amplify/core'
import awsmobile from './aws-exports'
import {withAuthenticator} from 'aws-amplify-react'
import '@aws-amplify/ui/dist/style.css'
import {listTodos} from './graphql/queries'
import API, {graphqlOperation} from '@aws-amplify/api'
import { Auth } from 'aws-amplify';

Amplify.configure(awsmobile)

const App: React.FC = () => {
  const [todos, setTodos] = useState<any>({})

  const getTodo = async() => {
    try {
      const res: any = await API.graphql(graphqlOperation(listTodos))
      console.log(res)
      setTodos(res.data.listTodos.items)
    } catch (e) {
      console.log(e)
    }
  }

  const ShowTodo = () => {
    if (todos.length > 0) {
      const todo = todos[0]
      return (
        <>
          <p>{ todo.id }</p>
          <p>{ todo.name }</p>
          <p>{ todo.description }</p>
          <p>{ todo.createdAt }</p>
          <p>{ todo.updatedAt }</p>
        </>
      )
    }
    return (
      <></>
    )
  }

  return (
    <>
      <h1>Todos</h1>
      <button onClick={() => getTodo()}>get todos!</button>
      <div>
        <ShowTodo/>
      </div>
    </>
  )
}
...
```

get todos! ボタンを押すと下に取得したデータが表示されます。
<img width="335" alt="スクリーンショット 2020-07-05 15.17.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/61ff584d-3af6-f7f4-d5e5-f0e690220e5e.png">

# cognito group による制御
AWS コンソールの Cognito で、グループを作成し、最初のユーザー認証で作成したアカウントに member グループを割り当てます。
<img width="354" alt="スクリーンショット 2020-07-05 15.30.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/99ccde98-2f5a-fe18-17a3-e662376f2fbd.png">

schema.graphql を以下のように変更します。

```graphql:schema.graphql
type Todo
  @model
  @auth(rules: [
    { allow: owner },
    { allow: groups, groups: ["admin"], operations: [read, update] }
  ])
{
    id: ID!
    name: String!
    description: String
}
```

これにより、member 権限のアカウントは Todo に対してなにもできなくなります。
一度ログアウトするために App.tsx を以下のように変更します。

```tsx:App.tsx
import React, {useState} from 'react'
import Amplify from '@aws-amplify/core'
import awsmobile from './aws-exports'
import {withAuthenticator} from 'aws-amplify-react'
import '@aws-amplify/ui/dist/style.css'
import {listTodos} from './graphql/queries'
import API, {graphqlOperation} from '@aws-amplify/api'
import { Auth } from 'aws-amplify';

Amplify.configure(awsmobile)

const App: React.FC = () => {
  const [todos, setTodos] = useState<any>({})

  const signOut = () => {
    Auth.signOut()
  }

  const getTodo = async() => {
    try {
      const res: any = await API.graphql(graphqlOperation(listTodos))
      console.log(res)
      setTodos(res.data.listTodos.items)
    } catch (e) {
      console.log(e)
    }
  }

  const ShowTodo = () => {
    if (todos.length > 0) {
      const todo = todos[0]
      return (
        <>
          <p>{ todo.id }</p>
          <p>{ todo.name }</p>
          <p>{ todo.description }</p>
          <p>{ todo.createdAt }</p>
          <p>{ todo.updatedAt }</p>
        </>
      )
    }
    return (
      <></>
    )
  }

  return (
    <>
      <button onClick={ () => signOut() }>ログアウト</button>
      <h1>Todos</h1>
      <button onClick={() => getTodo()}>get todos!</button>
      <div>
        <ShowTodo/>
      </div>
    </>
  )
}
...
```

以下のような画面になります。
一度ログアウトをすると、ログイン画面に遷移するので再度ログインします。
get todos! ボタンを押しても表示は変わらず、データが0件であることがわかります。
<img width="108" alt="スクリーンショット 2020-07-05 20.09.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/26282316-5e67-6c5d-6024-c6ff62d97939.png">
例えば、ここで schema.graphql の groups を admin から member に変更すると、データが取得できることを確認できます。

また、画面表示をグループごとに分けたいので、App.tsx に以下を追加しました。
初回でログインユーザーのグループ情報を取得し、その情報をもとに表示を出し分けました。
useEffect で初回のみユーザー情報を取得します（第2引数に [] を入れないと無限ループします）

```tsx:App.tsx
  const [group, updateGroup] = useState<any>({})

  const getUser = async() => {
    const user =  await Auth.currentAuthenticatedUser();
    updateGroup(user.signInUserSession.accessToken.payload["cognito:groups"])
    console.log(user.signInUserSession.accessToken.payload["cognito:groups"])
  }

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

  return (
    <>
      {
        group === 'admin' ? (
          <h1>管理者画面</h1>
        ) : (
          <h1>メンバー画面</h1>
        )
      }
      <button onClick={ () => signOut() }>ログアウト</button>
      <h1>Todos</h1>
      <button onClick={() => getTodo()}>get todos!</button>
      <div>
        <ShowTodo/>
      </div>
    </>
  )
```

<img width="211" alt="スクリーンショット 2020-07-05 19.54.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/a1201cc2-32d1-4899-6adc-c4417ce08d06.png">