Amplifyで未認証ユーザでもAppSyncからデータを取得できるようにする

Amplifyを使っていて、未認証(未ログイン)ユーザでAppSyncを接続しようとしたらno current userと出て、接続することができませんでした。解決するのにかなりつまったので、残しておきます。

# 実施する前の状態
- すでにAmplifyは構築されており、Auth, API(AppSync)も構築済み
- Authの認証方法はAMAZON_COGNITO_USER_POOLS

# 実施後の状態
- Authの認証方法はAWS_IAM
- 認証ユーザと未認証ユーザでIAMを設定
- 認証ユーザはAppSyncの全権限を持つPolicyが割り当てられている
- 未認証ユーザはAppSyncのQueryとSubscriptionの権限を持つPolicyが割り当てられている

# 手順
## IAM認証にし、認証されていないIDに対してアクセスを有効にする

```
$ amplify update auth
Please note that certain attributes may not be overwritten if you choose to use defaults settings.

You have configured resources that might depend on this Cognito resource.  Updating this Cognito resource could have unintended side effects.

Using service: Cognito, provided by: awscloudformation
 What do you want to do? Walkthrough all the auth configurations
 Select the authentication/authorization services that you want to use: User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage features for images or other content, Anal
ytics, and more)
 Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM) Yes # ここをYesにする
     --- （中略） ---
```

- これでCognitoのフェデレーティッドアイデンティティで認証されていないIDに対してアクセスを有効にするがチェックされます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/35b0fb48-edee-3189-ac45-e641b3939eb8.png)

## AppSyncの権限を持つPolicyを作成する
- `<project_root>/amplify/backend/api/<API name>/stacks/` 配下にCFnのコードを置くことでPolicyを追加することができます
  - カスタムリソースを使おうかと思いましたが、AppSyncApiIdはapi配下でないと取得できないので、こちらである必要があります
- 以下のようにIAMPolicy.jsonを作成しています 

```project_root/amplify/backend/api/API_name/stacks/IAMPolicy.json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "AppSync GraphQL Policy",
  "Metadata": {},
  "Parameters": {
    "AppSyncApiId": {
      "Type": "String",
      "Description": "The id of the AppSync API associated with this project."
    },
    "env": {
      "Type": "String",
      "Description": "The environment name. e.g. Dev, Test, or Production",
      "Default": "NONE"
    },
    "authRoleName": {
      "Type": "String"
    },
    "UnauthRoleName": {
      "Type": "String"
    }
  },
  "Resources": {
    "AuthAppSyncGraphQLPolicy": {
      "Type": "AWS::IAM::ManagedPolicy",
      "Properties": {
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "appsync:GraphQL"
              ],
              "Resource": [
                {
                  "Fn::Sub": [
                    "arn:aws:appsync:${AWS::Region}:${AWS::AccountId}:apis/${apiID}/*",
                    {
                      "apiID": {
                        "Ref": "AppSyncApiId"
                      }
                    }
                  ]
                }
              ]
            }
          ]
        },
        "ManagedPolicyName": {
          "Fn::Join": [
            "-",
            [
              "AuthAppSyncGraphQLPolicy",
              {
                "Ref": "env"
              }
            ]
          ]
        },
        "Roles": [
          {
            "Ref": "authRoleName"
          }
        ]
      }
    },
    "UnauthAppSyncGraphQLPolicy": {
      "Type": "AWS::IAM::ManagedPolicy",
      "Properties": {
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "appsync:GraphQL"
              ],
              "Resource": [
                {
                  "Fn::Sub": [
                    "arn:aws:appsync:${AWS::Region}:${AWS::AccountId}:apis/${apiID}/types/Query/*",
                    {
                      "apiID": {
                        "Ref": "AppSyncApiId"
                      }
                    }
                  ]
                },
                {
                  "Fn::Sub": [
                    "arn:aws:appsync:${AWS::Region}:${AWS::AccountId}:apis/${apiID}/types/Subscription/*",
                    {
                      "apiID": {
                        "Ref": "AppSyncApiId"
                      }
                    }
                  ]
                }
              ]
            }
          ]
        },
        "ManagedPolicyName": {
          "Fn::Join": [
            "-",
            [
              "UnauthAppSyncGraphQLPolicy",
              {
                "Ref": "env"
              }
            ]
          ]
        },
        "Roles": [
          {
            "Ref": "UnauthRoleName"
          }
        ]
      }
    }
  },
  "Outputs": {
    "EmptyOutput": {
      "Description": "An empty output. You may delete this if you have at least one resource above.",
      "Value": ""
    }
  }
}
```

- AuthRoleとUnauthRoleの定義をparameters.jsonに追加します

```project_root/amplify/backend/api/API_name/parameters.json
{
     --- （中略） ---
    "authRoleName": {
        "Ref": "AuthRole"
    },
    "UnauthRoleName": {
        "Ref": "UnauthRole"
    }
}
```

## 動作確認
- `amplify push` して問題なく、動作することを確認します
- IAMでPolicyが作成されていることが確認できます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/61a6512a-9ff6-e78b-5827-41ddd5a5a014.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/1d17b70c-2c3f-1d91-a873-453c9aae5af0.png)

- AuthRoleとUnauthRoleが上記で作成したPolicyが紐づいていることが確認できます
  - Role名はCognitoのフェデレーティッドアイデンティティで確認できます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/0c19a687-b136-48f0-e88a-fb580ba70bfc.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/402acbd1-1f90-0188-fb68-ce298afe68db.png)



# 参考
- [AWS Amplifyで、カスタムカテゴリを作って、カスタムリソースを追加してみた](https://thinkami.hatenablog.com/entry/2019/07/30/225425)
- [AWS Amplify+IAMで未認証ユーザでもAppSyncを使えるようにする](https://qiita.com/youn4101/items/9bca97083f07d1b9af12)