AWS Amplify + AppSync AWS_IAMで非認証ユーザーもQueryやmutationをとばせるようにする

# 本記事の対象者
- AWS AmplifyでAppSyncを使っている。
- GraphQLAPIを公開用にしたい。/非認証ユーザーでも使えるようにしたい。
- AppSyncの認証をAPI_KEYでしているが、手動でリフレッシュするのが嫌だ。

# はじめに
AppSyncを使ってGraphQL APIを公開する場合、何らかの方法で認証をする必要があります。
多くの場合、その手軽さからAPI_KEYを選択しがちです。
しかし、(Amplifyではなく)[AppSyncのドキュメント](https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/security.html#api-key-authorization)を見たところ、

>
API キーは、最大 365 日間有効に設定可能で、該当日からさらに最大 365 日、既存の有効期限を延長できます。API キーは、パブリック API の公開が安全であるユースケース、または開発目的での使用が推奨されます。
>

とのこと。
くわえて[こちらのIssue](https://github.com/aws-amplify/amplify-cli/issues/5843)よると、一応API_KEYのリフレッシュ方法が案内されているものの、おそらく現在は手動でしかできないようです。[^1]

ほかの選択肢として`OPENID_CONNECT認証`と`AMAZON_COGNITO_USER_POOLS認証`がありますが、この２つは何らかの形でログインが必要になります。

というわけで今回は認証に`AWS_IAM`を指定して、API_KEYじゃなくても、非認証ユーザーがQueryとmutationを飛ばせるようにしていきます。  

# 理屈
1.認証をAWS_IAMにする
2.すると非認証ユーザーに対してCognito Identity Poolsの**UnAuthenticated Role**が付与される。
３．その**UnAuthenticated Role**に対してqueryとmutationのポリシーをアタッチ。

なお、amplifyではschemaの@authディレクティブに応じたポリシーがアタッチされた
**Authenticated Role**及び、**UnAuthenticated Role**が自動で生成されます。

# 環境
- React 17.0.1
- TypeScript 4.0.3
- **aws-amplify 3.3.13**
- **amplify-cli 4.40.1**

```shell
$ amplify init
```
とフロントのセットアップは済ませたとして勧めていきます。

# リソースの追加
## authの追加  
```shell
$ amplify add auth
```
でauthモジュールを追加します。途中
Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM)という項目が出てくるので、**必ずYesにしてください**

>Do you want to use the default authentication and security configuration? (Use arrow keys):

細かい設定をしたいので
**Manual configuration**

> Select the authentication/authorization services that you want to use: (Use arrow keys)

:**User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage features for images or other content, Analytics, and more)**

>Please provide a friendly name for your resource that will be used to label thi
s category in the project

:任意のラベル名

>Please enter a name for your identity pool.

:任意のidentity pool名

>Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM)

今回の肝。必ず
:**Yes**

以降はお好みで大丈夫です。強いて言うなら3rd party authenticationやOAuthは特に無いのでNoでいいです。

## APIの追加

```shell
$ amplify add api
```
でapiを作成します。

>
Choose the default authorization type for the API
>

の項目は **IAM**を選択してください。ほかは任意で大丈夫です。

#schemaの編集
## directiveの設定
今回は未認証ユーザーがqueriesとmutationのcreateを使えるように指定します。

```js
type Todo
  @model(mutations: { create: "createTodo" }, subscriptions: null)
  @auth(rules: [{ allow: public, provider: iam }]) {
  id: ID!
  name: String!
  description: String
}
```

`@auth`部分がみそです。
[ドキュメント](https://docs.amplify.aws/cli/graphqltransformer/overview#public-authorization)のpublic authorizationの欄にサンプルがあります。それによると、

>
auth ディレクティブは、指定された認証モードのデフォルトのプロバイダを上書きすることができます。上記のサンプルでは、APIキーの代わりにCognito Identity Poolsの「UnAuthenticated Role」をパブリックアクセスに使用できるプロバイダとしてiamが指定されています。amplify add authと一緒に使用すると、CLIは「UnAuthenticated」ロール用にスコープダウンされたIAMポリシーを自動的に生成します。
>

つまり

`allow: public`:対象は非認証(public)ユーザーだよ
`provider: iam`:対象が非認証ユーザーなのでiamはUnAuthenticatedロールを付与

ということを表しています。

また、@modelで`query全て`と`mutationのcreate`のみ生成するように指定しており、push時に生成されるUnAuthenticatedロールに自動的にそれらの実行権限が付与されます。

## push
それでは

```shell
$ amplify push
```
してクラウドにpush + GraphQLコードの生成しましょう。

>
Do you want to generate code for your newly created GraphQL API
>

**Yes**

>
Choose the code generation language target
>

今回はフロントの都合でTypeScriptにします。
あとはデフォルトで大丈夫です。

# Roleの確認
## auth/unauth role
リソースの反映が終わったらRoleの作成ができているか、確認しましょう。
Amplify Consoleへいき、バックエンドのページからCognitoへ飛びましょう。
![role.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522425/d79eef5d-c044-3238-51e6-bef65bcd2c2d.png)
そして右上の**IDプールの編集**へ飛び、
![role-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522425/add9f57c-d6f3-a421-ffac-f1c9e7b5eafb.png)

**amplify-(プロジェクト名)-(環境名)-(数字?)-(unauth/auth)**
のロールが設定されています。
このロールに対してポリシーが正しく設定されているかも確認しようと思います。

## policy
IAMのコンソールで先程のロールを検索にはっつけてアクセス権限を見ると、たしかに
- getTodo
- listTodo
- createTodo
のリソースが指定されていると思います。
ここまでで、ひとまずバックエンドの編集は完了です。

#フロントの編集
ここからフロントの編集をしてきます。

```shell
$ yarn add aws-amplify
```
を行っていることを前提とします。

```js
//index.tsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import Amplify from "aws-amplify";
import config from "./aws-exports";
Amplify.configure(config);

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

```

```js
//App.tsx
import React from "react";
import API from "@aws-amplify/api";
import * as mutations from "./graphql/mutations";
import * as queries from "./graphql/queries";
import { GRAPHQL_AUTH_MODE } from "@aws-amplify/api-graphql/lib/types";
//TypeSciprtの方は↑に注意

function App() {
  const todoDetails = {
    name: "test",
    description: "test",
  };
  const generateTodo = async () => {
    try {
      await API.graphql({
        query: mutations.createTodo,
        variables: { input: todoDetails },
        authMode: GRAPHQL_AUTH_MODE.AWS_IAM,//TypeScriptの場合のみ　それ以外は"AWS_IAM"
      });
    } catch (e) {
      console.error(e);
    }
  };
  const getList = async () => {
    try {
      console.log(
        await API.graphql({
          query: queries.listTodos,
          authMode: GRAPHQL_AUTH_MODE.AWS_IAM,//TypeScriptの場合のみ　それ以外は"AWS_IAM"
        })
      );
    } catch (e) {
      console.error(e);
    }
  };

  return (
    <>
      <button onClick={getList}>Query</button>
      <button onClick={generateTodo}>Mutation</button>
    </>
  );
}

export default App;
```
認証モードがAWS_IAMのときは、graphqlOperation関数は使用しません。その代わり、

```js
await API.graphql({
      query: queries.listTodo,
      authMode: GRAPHQL_AUTH_MODE.AWS_IAM, //TypeScriptの場合のみ　それ以外は"AWS_IAM"
    });
```
authModeパラメータを含んだオブジェクトを渡します。[ドキュメントはこちら]
(https://docs.amplify.aws/lib/graphqlapi/authz/q/platform/js)

ここで、プロジェクトにTypeScriptを使っている方は、**`GRAPHQL_AUTH_MODE`という型をimportして**authModeに`GRAPHQL_AUTH_MODE.AWS_IAM`を渡してください。
それ以外の方はドキュメント通り、文字列`"AWS_IAM"`で大丈夫です。
この現象に関しては現在[Issue](https://github.com/aws-amplify/amplify-js/issues/7460)をとばして確認しています。[^2]

# 動作確認
では実際に動作するか確認します。
mutationをとばすと....
![スクリーンショット 2020-12-31 15.12.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522425/3b7e49e6-6b3a-69c1-4ea1-ff62e83b9636.png)
DynamoDBに要素が追加されました。
続いて、qureyをとばすと....
![スクリーンショット 2020-12-31 15.14.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522425/2d908852-8f86-c6ac-c7f6-e7c5efa19d62.png)
確かにコンソールにデータが表示されました。

今回は非常に単純なschemaとAppでしたがご容赦ください。

# ソースコード
[こちら](https://github.com/togami2864/amplify_IAM)[^3]


[^1]: 質問者の「手動でする必要があるということですか？！」より先は返信が途絶えている...

[^2]: 原因は結構明らかでGRAPHQL_AUTH_MODEは文字列列挙型(enum)になっており、そこにstringを入れようとするのでエラーになります。

[^3]: awsのリソースは消えているのでcloneしても実際のAPI操作はできません。