Amplify Gen2(v6)でのデータ認証ルールと `observeQuery` の正しい使い方

― 未認証ユーザー・認証ユーザー(オーナー/非オーナー)で“見えるデータ”を正しく設計する方法

Amplify Gen2(v6)の Data モデル認証はとても強力ですが、初見では挙動が直感的ではありません。

特に、掲示板アプリなど「未認証でも閲覧可能」「認証済みなら投稿可能」という一般的な要件で、

  • 認証後に逆に一覧が見えなくなる
  • observeQuery の結果が count: 0 になる
  • guest では見えるのに authenticated では見えない
  • owner をつけたらエラーは消えたが理由がわからない

といった現象にハマりやすく、多くの開発者が混乱します。

本記事では、公式ドキュメントの内容と実際のGen2の挙動を踏まえ、

  • 認証ルールの正しい書き方
  • observeQuery と authMode の関係
  • owner ルールの落とし穴
  • 未認証/認証ユーザーで何が “見える” のか

体系的に理解できる実践ガイド としてまとめました。


1. Amplify Gen2 認証モデルの基本思想

■ すべては “deny by default”

Amplify Gen2 の Data は 許可された操作以外すべて拒否 します。

allow.guest().to(['get', 'list'])

と書けば guest の get/list は OK、 それ以外は 全部 NG

■ ルールは OR 条件で評価される

allow.guest()
allow.authenticated()
allow.owner()

どれか1つでも通れば認可される仕組みです。

■ owner を使うなら owner: a.string() が必須

モデルに owner フィールドがなければ owner 認証は機能しません。


2. なぜ認証すると「見えなくなる」現象が起きるのか?

次のケースが典型的です。

allow.guest().to(['get', 'list'])
allow.owner()

一見正しく見えますが…

✔ 未認証(identityPool)

  • guest → 適用される → 一覧が全件見える

✔ 認証済み(userPool)

  • guest → 適用されない!!
  • owner → 自分の投稿だけ
  • authenticated → 未定義

他人の投稿がすべて消える

これが「認証したら見えなくなる」原因です。

Amplify Gen2 は「認証済み=guestルールを継承する」仕組みではありません。


3. 認証済みユーザーも “公開データ” を読めるようにするには?

結論:authenticated にも guest と同等の read 権限を付与する

allow.guest().to(['get', 'list', 'listen', 'sync'])
allow.authenticated().to(['get', 'list', 'listen', 'sync'])
allow.owner().to(['update', 'delete'])

これが 最も安全で混乱がない 書き方です。


4. observeQuery と authMode の関係を理解する

observeQuery 呼び出し例

client.models.BoardPost.observeQuery({ authMode })

ここでの authMode は AppSync へ どの認証方法でアクセスするか を指定しています。

authMode 実際の認証 想定ケース
userPool Cognito User Pool トーク 認証済みユーザー
identityPool Cognito Identity Pool (未認証扱い) 未認証ユーザー
apiKey パブリック API Key 公開API
iam IAM (SigV4) サーバー用途

authMode を間違えると何が起きる?

認証済み + authMode = userPool

→ authenticated / owner のルールのみ評価 → guest のルールは 完全に無視

認証済み + authMode = identityPool

→ guest のルールが評価される(未認証扱い) → 一覧が見える(ただし create 不可)


5. 正しい authMode 戦略

掲示板のように「未認証=閲覧OK」「認証済み=閲覧+投稿OK」の場合、

状態 authMode 期待する振る舞い
未認証 identityPool guest の読み取りルールで閲覧可
認証済み userPool authenticated で読み取り+create 可

※ guest ルールと authenticated ルールを明示しておかないと 認証後に読み取りが消えるので注意。


6. 掲示板アプリの正しいモデル定義(完全版)

BoardPost: a.model({
  author: a.string(),
  message: a.string().required(),
  imageKeys: a.string().array(),
})
.authorization(allow => [
  // 未認証ユーザーでも一覧取得 OK
  allow.guest().to(['read']),

  // 認証済みユーザーも同等に閲覧
  allow.authenticated().to(['read']),

  // 自分の投稿のみ作成・更新・削除可能
  allow.owner().to(['get', 'create', 'update', 'delete']),
])

これだけで、

  • 未認証:閲覧OK(投稿不可)
  • 認証済み:閲覧OK
  • 投稿・編集・削除:owner のみ

という自然な挙動になります。


🔧【修正】7. owner フィールドがないと起きる“悲劇”

※この記事公開後の追加検証により、内容の一部に誤りがあることが分かったため、 2025-11-27 に本節を修正しました。

owner ルールを使う場合、以下が必須です。

~~owner: a.string()~~

さらに create 時に owner を入れないと…

  • update / delete が全拒否
  • observeQuery が owner 不一致で非表示
  • Unauthorized(401/403)が発生
  • リアルタイム購読が落ちる

Amplify は create 時に自動で owner を設定しません。
(AppSync の v1 の時とは異なります)

必ず自前で owner を渡す必要があります

❌【旧結論】owner フィールドがないと owner 認証は動かない

(これは誤りでした)

✅【新結論】Amplify Gen2(v6)の allow.owner()

authMode が userPool のとき、owner フィールドを明示しなくても自動で付与・セットされる

今回の調査で判明したのは、

owner が自動で入らなかった理由は 「owner フィールドを書かなかった」からではなく 「authMode が userPool になっていなかった」から

という点です。


✔ なぜ owner フィールドを明示しなくても動くのか?

Amplify Data v6 では、

  • allow.owner() を使う
  • create が userPool の JWT で実行される

この 2 条件が揃った場合、AppSync resolver が

owner: "<Cognitoユーザーのsub:username>"

を自動で追加します。

つまり、以下のような定義でも owner は自動で付きます:

BoardPost: a.model({
  author: a.string(),
  message: a.string(),
})
.authorization(allow => [
  allow.owner(), // owner フィールドは自動追加される
])

✔ 今回 owner が入らなかった本当の原因

調査の結果、owner が空だった理由は次の 2 点でした:

① create の authMode が userPool ではなかった

identityPool のまま create が実行されていたため、 Amplify は「ユーザーの identity が確定しない」と判断して owner を付加しません。

② owner ルールに create が含まれていなかった

allow.owner().to(['update', 'delete']) だと、 create 時に owner 自動セット処理が発火しないため、owner が空のまま残ってしまう。


✔ 修正後(authMode=userPool)では owner が自動セットされるようになった

authMode と認可ルールを正しく直したあとは、 DynamoDB に以下のように owner が問題なく入るようになりました。

"owner": "cognitoUserSub:username"

✔ 修正まとめ(この記事への変更点)

【変更前の理解】

  • owner フィールドが無ければ owner 認証は動かない
  • owner 自動セットはされないため owner: a.string() は必須

【変更後の正しい理解】

  • owner 自動セットは authMode=userPool で create した場合にのみ動作
  • owner: a.string() は必須ではない(あっても良いが、無くても動く)
  • owner が空だった原因は認証モードの問題だった
  • allow.owner().to(['create']) の指定も重要 (create が owner ルールに含まれないと owner 自動付与ロジックが動かない)

✔ この記事を読んでいる開発者の方へ(注意点)

Amplify Gen2 の owner は、

authMode が userPool で create が owner ルールに一致するときのみ自動セットされる

という振る舞いであり、 「owner フィールドの有無」ではなく「認証方式」の影響が極めて大きい点に注意が必要です。


8. 最終まとめ:Amplify Gen2 の認証は「guest と authenticated を明示せよ」

Amplify Gen2 はデフォルトが “すべて拒否” のため、

  • guest には guest の権限
  • authenticated には authenticated の権限
  • owner には owner の権限

それぞれ明示的に書くことが重要 です。

特に observeQuery は authMode とルールの食い違いに敏感で、 設定がズレると簡単に「0件」になってしまいます。

✔ 正しいモデル定義にすることで…

  • 未認証でも一覧閲覧OK
  • 認証済みでも全投稿閲覧OK
  • 自分の投稿のみ編集OK
  • observeQuery も安定して動作

という理想的な掲示板アプリが構築できます。