togami2864

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

2020-12-30

本記事の対象者

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

はじめに

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

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

とのこと。

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

ほかの選択肢として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 this 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ロールを付与
ということを表しています。

また、@modelでquery全て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をとばして確認しています
注):原因は結構明らかでGRAPHQL_AUTH_MODEは文字列列挙型(enum)になっており、そこにstringを入れようとするのでエラーになります。

動作確認

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

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

dynamoDBのデータを取得できました。

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

ソースコード

こちら
注): awsのリソースは消えているのでcloneしても実際のAPI操作はできません。