この記事は「Amplify x Next.jsハマりどころ」シリーズ第2弾です。
もともとはnoteに書いていたのですが、あまり見てもらえないのとコード記述が読みにくいのでqiitaにせっせと移植中です。
ハマりどころ一覧
- postConfirmation lambda triggerでstorage(dynamoDB)にアクセスしようとすると、Circular dependency between resourcesでpushできない
- type Query fieldに@aws_iamディレクティブをつけても、authRole(またはunauthRole)にポリシーがアタッチされない <- 今回!
- type Mutation filedに@authディレクティブをつけるとOnly one resolver is allowed per field. (Service: AWSAppSync; Status Code: 400; Error Code: BadRequestException;)
- redirect_urlを環境ごとに分けたいんですが
- S3の画像URLを署名なしで取得したいんですが
- aws-exports.jsが.gitignoreの対象になっているためビルド時にエラーになるんですが
- モノレポでamplifyバックエンドを共有したいんですが
- サブドメインでも認証を維持したいんですが
今回はNo.2について書きます。
※このシリーズでは、まずはエラーを再現し、そのエラーを修正するというチュートリアル的な流れで書いていきます。結論だけ知りたい方は結論まで読み飛ばしてください。
Versions
@aws-amplify/cli: 4.43.0
To Reproduce
Amplify api categoryには、様々なディレクティブが用意されていてgraphql schemaに追加の機能を指定することができます。その中に、@functionディレクティブというものがあり、これはlambdaをAWS AppSync APIのresolverに割り当てることを簡単に実現してくれます。例えばこんな感じ。
# schema.graphql
type Query {
echo(msg: String): String @function(name: "echofunction-${env}")
}
// ./amplify/backend/function/echofunction/src/index.js
exports.handler = async (event) => {
return event.arguments.msg
}
// ./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レスポンスを返します。
{
"errors" : [ {
"errorType" : "UnauthorizedException",
"message" : "Permission denied"
} ]
}
これは、graphql.schemaで記述したトップレベルのtype Queryフィールドに、AWS_IAM認可で実行できるようにする@aws_iamディレクティブをamplifyは自動で付与してくれないためです(ちなみに@aws_iamはamplifyではなくAWS AppSyncで用意されたディレクティブです)。なので以下のようにgraphql.schemaを書き換えてみます。
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の追加作業を良しなにやってくれます。
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/pull/3590