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


本記事の対象者

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

はじめに

AppSyncを使ってGraphQL APIを公開する場合、何らかの方法で認証をする必要があります。
多くの場合、その手軽さからAPI_KEYを選択しがちです。
しかし、(Amplifyではなく)AppSyncのドキュメントを見たところ、

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

とのこと。
くわえてこちらのIssueよると、一応API_KEYのリフレッシュ方法が案内されているものの、おそらく現在は手動でしかできないようです。1

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

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

理屈

1.認証をAWS_IAMにする
2.すると非認証ユーザーに対してCognito Identity PoolsのUnAuthenticated Roleが付与される。
3.その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
$ amplify init

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

リソースの追加

authの追加

$ 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の追加

$ amplify add api

でapiを作成します。

Choose the default authorization type for the API

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

schemaの編集

directiveの設定

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

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

@auth部分がみそです。
ドキュメントのpublic authorizationの欄にサンプルがあります。それによると、

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

つまり

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

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

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

push

それでは

$ 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へ飛びましょう。

そして右上のIDプールの編集へ飛び、

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

policy

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

フロントの編集

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

$ yarn add aws-amplify

を行っていることを前提とします。

//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")
);

//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関数は使用しません。その代わり、

await API.graphql({
      query: queries.listTodo,
      authMode: GRAPHQL_AUTH_MODE.AWS_IAM, //TypeScriptの場合のみ それ以外は"AWS_IAM"
    });

authModeパラメータを含んだオブジェクトを渡します。ドキュメントはこちら

ここで、プロジェクトにTypeScriptを使っている方は、GRAPHQL_AUTH_MODEという型をimportしてauthModeにGRAPHQL_AUTH_MODE.AWS_IAMを渡してください。
それ以外の方はドキュメント通り、文字列"AWS_IAM"で大丈夫です。
この現象に関しては現在Issueをとばして確認しています。2

動作確認

では実際に動作するか確認します。
mutationをとばすと....

DynamoDBに要素が追加されました。
続いて、qureyをとばすと....

確かにコンソールにデータが表示されました。

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

ソースコード

こちら3


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

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

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