【Amplify x Next.jsハマりどころ】type Query fieldに@aws_iamディレクティブをつけても、authRoleにpolicyがアタッチされない

この記事は「Amplify x Next.jsハマりどころ」シリーズ第2弾です。
もともとは[note](https://note.com/knagata/n/n19c23637e645)に書いていたのですが、あまり見てもらえないのとコード記述が読みにくいのでqiitaにせっせと移植中です。

## ハマりどころ一覧
1. [postConfirmation lambda triggerでstorage(dynamoDB)にアクセスしようとすると、Circular dependency between resourcesでpushできない](https://qiita.com/kazukinagata/items/664bf168b229c987dee0)
2. type Query fieldに@aws_iamディレクティブをつけても、authRole（またはunauthRole）にポリシーがアタッチされない <- 今回！
3. type Mutation filedに@authディレクティブをつけるとOnly one resolver is allowed per field. (Service: AWSAppSync; Status Code: 400; Error Code: BadRequestException;)
4. redirect_urlを環境ごとに分けたいんですが
5. S3の画像URLを署名なしで取得したいんですが
6. aws-exports.jsが.gitignoreの対象になっているためビルド時にエラーになるんですが
7. モノレポでamplifyバックエンドを共有したいんですが
8. サブドメインでも認証を維持したいんですが

今回はNo.2について書きます。

※このシリーズでは、まずはエラーを再現し、そのエラーを修正するというチュートリアル的な流れで書いていきます。結論だけ知りたい方は結論まで読み飛ばしてください。

## Versions
@aws-amplify/cli: 4.43.0

## To Reproduce

Amplify api categoryには、様々なディレクティブが用意されていてgraphql schemaに追加の機能を指定することができます。その中に、@functionディレクティブというものがあり、これはlambdaをAWS AppSync APIのresolverに割り当てることを簡単に実現してくれます。例えばこんな感じ。

```graphql
# schema.graphql

type Query {
 echo(msg: String): String @function(name: "echofunction-${env}")
}
```

```javascript
// ./amplify/backend/function/echofunction/src/index.js

exports.handler = async (event) => {
 return event.arguments.msg
}
```

```javascript
// ./page/index.js
import React from  'react'
import { Amplify, API } from 'aws-amplify'
import awsExports from '../src/aws-exports'
import { echo } from '../src/graphql/queries'

Amplify.configure({ ...awsExports, ssr: true })

export default Home() {
  const [msg, setMsg] = useState('')
  useEffect(() => {
    (async () => {
      try {
        const response = await API.graphql({
          query: echo,
          variables: {
            msg: 'hello',
          },
        })
        console.log(response.data.echo)
        setMsg(response.data.echo)
      } catch (error) {
        console.log(error)
      }
    })()
  }, [])

  return (<div>{ msg } < /div>)
}
```

ところでAmplify apiをcallする際には、必ず

- API_KEY
- AWS_IAM
- OPENID_CONNECT
- AMAZON_COGNITO_USER_POOLS

という4つのauthorization typesのどれかで認可される必要があります。`amplify api add/update`でdefault authorization typeを指定することができ、通常この認可モードでapiが呼ばれます。一般的にはdefaultをAWS_IAM、セカンダリとしてAMAZON_COGNITO_USER_POOLSを使用する場合が多いのではないかと思います。

さて、default authorization type をAWS_IAMにしていた場合、上の`API.graphql()`は401レスポンスを返します。

```json
{
 "errors" : [ {
   "errorType" : "UnauthorizedException",
   "message" : "Permission denied"
 } ]
}
```

これは、graphql.schemaで記述したトップレベルのtype Queryフィールドに、AWS_IAM認可で実行できるようにする@aws_iamディレクティブをamplifyは自動で付与してくれないためです（ちなみに@aws_iamはamplifyではなくAWS AppSyncで用意されたディレクティブです）。なので以下のようにgraphql.schemaを書き換えてみます。

```graphql
type Query { 
  echo(msg: String): String @function(name: "echofunction-${env}") @aws_iam
}
```

これでめでたしめでたしかと思いますが、実はまだclient側では引き続きUnauthorizedExceptionが発生します。

clientからのIAM認可モードでのapiは、amplifyが自動で作成してくれるauthRole（およびunauthRole）を通して呼び出されます。このroleにAppSync APIを実行するpolicyが割り当てられているのですが、@aws_iamディレクティブを使用したフィールドに関しては自動的にpolicyに追加されません。そのため、authRole（またはunauthRole）にQuery.echoを実行する権限がないということになります。

## こいつの倒し方

@aws_iamではなく@authディレクティブを使用します。@authディレクティブはAmplifyで追加されたディレクティブで、policyの追加作業を良しなにやってくれます。

```graphql
type Query { 
  echo(msg: String): String @function(name: "echofunction-${env}")
    @auth(rules: [
      { allow: private, provider: iam },
      { allow: public, provider: iam }
    ])
}
```

## 最後に

いやいや、最初から@authディレクティブ使えよ！ってオチなんですが、実は@authディレクティブを@modelと一緒にしか使えない時代があったんです。なので、上の方法でcliに怒られた場合、@aws-amplify/cliのバージョンを上げてみてください。

[https://github.com/aws-amplify/amplify-cli/issues/2701](https://github.com/aws-amplify/amplify-cli/issues/2701)
[https://github.com/aws-amplify/amplify-cli/pull/3590](https://github.com/aws-amplify/amplify-cli/pull/3590)