強欲で謙虚なツボツボ

趣味の読書の書の方

【AWS】Amplifyで作成されたAppSyncのGraphQLを外部から発動したい。(API Gateway → Lambda → AppSync → DynamoDB)

概要

AWS Amplifyを用いるとCLIのおかげで、コマンド実行するだけでAWSのいろんなサービスをよしなに構築してくれて、難しいことはあまり考えなくてもアプリケーションが作れる。

とりあえず公式ドキュメントに倣って、API(GraphQL), Authentication, の機能を作成することが容易にできた。
amplify-cliのコマンドを実行するだけで、Cognito, AppSync, DynamoDBを構築してくれている。AWSでサービスを組み合わせるのをやってくれるのは非常にありがたい。(コンソール画面での設定の仕方とかも入力項目が多くて正直よくわからないから)

https://docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/ios

https://docs.amplify.aws/lib/auth/getting-started/q/platform/ios

そんな中、勝手に構築してくれたAppSyncのGraphQLをCognito認証がない外部アプリからもやりたくなった。
とりあえず、Lambdaを使えばいいんだろうことは想像できたけど詳しいことは調べないとわからなかった。AppSyncでIAMでの認可するのもどうやるのか調べながらだった。

 

目次

長くなりそうなので目次です。

 

大まかな流れ

1. まず、AppSyncのAPI設定で認証プロバイダーにIAMを設定する。
(amplify-cliでやった時にcognitoをデフォルトで設定したので、IAMでもいけるようにする)

2. AppSyncでGraphQLのスキーマを変更
スキーマに追加した認証プロバイダーに関する追記を行う)

3. AppSyncのAPI(GraphQL)にリクエストを送るLambda関数(Node.js)を作成する。
(amplify-cliで作成されたcreateUserとかを実行させてDynamoDBを操作する)

4. 好きなところからLambda関数を呼び出すためにAPI Gatewayを作成する。
(Lambdaを実行するトリガーにAPI Gatewayを設定する)

 

ほぼ全部AWSのコンソール画面で操作します。

好きな順番でやっていいも大丈夫だと思います。(2の前に1は必須)

 

AppSyncのAPI設定で認証プロバイダーにIAMを設定

AWS AppSyncより作成したAPIを選択して左ナビの設定から設定画面へ行きましょう。

追加の認証プロバイダーより、Newを押して表示されるダイアログで追加するプロバイダーを設定します。

f:id:taopo:20201115095100p:plain

認証プロバイダーにIAMを追加する

 

f:id:taopo:20201115094923p:plain

デフォルトにcognitoが設定されていて、IAMを追加した

参考: Cognito認証されているAppSyncをIAM認証で後ろから叩く - Qiita

 

GraphQLのスキーマを変更

認証プロバイダーにIAMを追加しましたが、このままではデフォルトで設定されているものしか使用されないので、GraphQLのスキーマを変更します。

同じAppSync画面の左ナビのスキーマからスキーマ編集画面へ行きましょう。

外部から実行したいクエリに以下のように追記します。
※ スペースを入力するとエラーが何故かエラーが出ることがありますのでコピペ安定かもです。

# Before
type Mutation {
	createUser(input: CreateUserInput!): User
	updateUser(input: UpdateUserInput!): User
	...
}
# After
type Mutation @aws_cognito_user_pools @aws_iam {
	createUser(input: CreateUserInput!): User
	updateUser(input: UpdateUserInput!): User
	...
}

記述の意味は、Mutationを実行するのにCognitoとIAMを認証プロバイダーとして使用するというものです。
Beforeのなにも書いてない状態はデフォルトが反映されています。(僕の場合はcognitoがデフォルトなので@aws_cognito_user_poolsがデフォルトでついて記述されているようなもの)

記述しない場合がデフォルトというだけなので、記述する場合はデフォルトのもの含めて設定したいものを全て記述する必要があります。

今回はMutationだけでいいのでこれだけしか編集していませんが、Queryにも同様に設定したり個別に設定できます。

# MutationのcreateUserはCognitoとIAM、updateUserはIAMだけ
type Mutation {
    createUser(input: CreateUserInput!): User
        @aws_cognito_user_pools @aws_iam
    updateUser(input: UpdateUserInput!): User
        @aws_iam
    ...
}

# Queryはcognitoだけ
type Query @aws_cognito_user_pools {
    getUsers(id: ID!): User
	listUsers(filter: ModelUsersFilterInput, limit: Int, nextToken: String): ModelUserConnection
	...
}

編集後は画面右上の「スキーマを保存」を忘れずに押してください。

参考: Cognito認証されているAppSyncをIAM認証で後ろから叩く - Qiita

 

Lambda関数を作成

関数を新規作成

一から作成で設定はいじらずに作成します。

f:id:taopo:20201115190655p:plain

Lambda関数を作成

 

関数にポリシーを付与

次に、LambdaがAppSyncのGraphQLを実行できるように、作成した関数にAppSyncのポリシーを付与します。
関数のアクセス権限タブを選択して、実行ロールからロールを以下のように編集します。

f:id:taopo:20201115192538p:plain

関数のアクセス権限より、実行ロールを選択

 

f:id:taopo:20201115192629p:plain

実行ロールにインラインポリシーの追加

 

f:id:taopo:20201115192812p:plain

サービスはAppSync、アクションは書き込みのGraphQLを選択

 

f:id:taopo:20201115192906p:plain

AppSyncの設定画面にあるAPI IDを入力、アカウントは自動で入ります

 

f:id:taopo:20201115193033p:plain

こんな警告が出ますが、無視していいいみたいです

 

関数を実装 

ポリシーを付与したら、関数を作成します。index.jsを直接編集してDeployを押せば良いです。
作成した関数は以下のような感じです。

こちらが非常に参考になりました。↓
 AlexaのPersistent AttributesをAppSyncからGraphQLで取得する | WP Kyoto

require('isomorphic-fetch');
const aws = require('aws-sdk');
const AWSAppSyncClient = require('aws-appsync').default;
const AUTH_TYPE = require('aws-appsync/lib/link/auth-link').AUTH_TYPE;
const gql = require('graphql-tag');

const url = 'AppSyncの設定画面に記載されているAPI URL';
const region = 'ap-northeast-1';
const auth_type = AUTH_TYPE.AWS_IAM; // AWS_IAMの部分が認証プロバイダーです。cognitoならば、AUTH_TYPE.AMAZON_COGNITO_USER_POOLS


async function createUser(data) {
    
    const variables = {
        "createUserInput": { // ここのcreateUserInputという名前は、以降にあるcreateUserGqlの$createUserInputと同じ名前にする必要があります。
            // graphQLスキーマにcreateUser(input: CreateUserInput!): Userとあったので、
            // ここにはCreateUserInputと同じ構造で記述する。
            // ~~Inputは編集したAppSyncのスキーマに書かれています。
            "username": data.username
        }
    }
    
    const createUserGql = gql(`
        mutation createUser($createUserInput: CreateUserInput!) { // さっきのvariables内にあったcreateUserInputがここに入る
            createUser(input: $createUserInput) {
                username
            }
        }`
    );
    
    const client = new AWSAppSyncClient({
        url: url,
        region: region,
        auth: {
            type: auth_type,
            credentials: aws.config.credentials
        },
        disableOffline: true
    });

    try {
        const result = await client.mutate({
            mutation: createUserGql,
            variables
        });
        console.log(JSON.stringify(result));
        return { status: "OK", result: result };
    } catch (err) {
        console.log(JSON.stringify(err));
        return { status: "Error", result: err };
    }

};

/**
* リクエストのbodyは以下のようなものを想定しています。
* { "mutation": "createUser",
*   "data": { "username": "ユーザーメイ" }
* }
*/
exports.handler = async (event, context) => {

    let body;
    let statusCode = 200;
    const headers = {
        'Content-Type': 'application/json',
    };

    try {
        switch (event.httpMethod) {
            case 'DELETE':
                throw new Error(`Unsupported method "${event.httpMethod}"`);
            case 'GET':
                throw new Error(`Unsupported method "${event.httpMethod}"`);
            case 'POST':
                const bodyJson = JSON.parse(event.body);
                if (bodyJson.mutation == 'createUser') {
                    const res = await createUser(bodyJson.data);
                    body = res;
                } else {
                    throw new Error(`Unsupported mutation "${bodyJson}". Invalid request parameters "${bodyJson}"`);
                }
                break;
            case 'PUT':
                throw new Error(`Unsupported method "${event.httpMethod}"`);
            default:
                throw new Error(`Unsupported method "${event.httpMethod}"`);
        }
    } catch (err) {
        statusCode = '400';
        body = err.message;
    } finally {
        body = JSON.stringify(body);
    }

    return {
        "statusCode": statusCode,
        "headers": headers,
        "body": body
    };
};

node_modulesが必要

ただ、このままでは機能しません。
requireで読み込むモジュールを用意しなければなりません。(npm installしたnode_modules)

この手順のみAWSコンソールの操作ではないです。

まず、適当な場所にnodejsという名前でフォルダを作成。

次に、先ほどのindex.jsでrequireしているものをnpm install する(isomorphic-fetch, aws-sdk, aws-appsync, graphql-tag)
aws-appsyncはversionを@^1.0.0した方がいいです。そのまま最新バージョンをinstallしたら

require('aws-appsync/lib/link/auth-link').AUTH_TYPE;

できませんでした。

そして、nodejsを圧縮してnodejs.zipを作成します。

これを作成したlambda関数のレイヤーとして設定します。

こちらが非常に参考になりました↓

node_modulesをLambdaレイヤーにアップロードしてライブラリを使用する - Qiita

f:id:taopo:20201115202052p:plain

Lambdaのレイヤーより作成を選択

 

f:id:taopo:20201115202126p:plain

作成したnodejs.zipをアップロードし、ランタイムはNode.jsを選択

f:id:taopo:20201115202651p:plain

index.jsを編集した画面に戻り、レイヤーを押すとレイヤーの追加ができる

 

f:id:taopo:20201115202759p:plain

先ほど作成したレイヤーを選択、バージョンは1でよい

 

API Gatewayを作成

最後にLambda関数のトリガーを追加からAPI Gatewayを設定すれば完成です。
トリガーの追加から設定すると勝手にAPI Gatewayを作成してLambda関数と紐づけてくれます。

f:id:taopo:20201115203317p:plain

トリガーを追加より、API Gatewayを選択

API Gatewayが作成され、GET, POST等の全てに対応したAPIが作成されます。
作成されたものを特にいじる必要はないと思います。(リクエストのパラメータとかいろいろ調整したい場合は別)

セキュリティでAPIキーを選択すると、APIキーが作成されAPIとキーを紐付けるのも自動でやってくれています。

f:id:taopo:20201115204230p:plain

Lambdaにより作成されたとなっています。APIキーは表示を押すと確認できます。

 

動作を確認しよう

APIのURLは、左ナビのステージよりURLの呼び出しと書いてあるのがURLです。

とりあえずcurlなどで動作確認してみましょう。
今回の場合は以下のようになります。

 curl -H "x-api-key: 作成されたAPIキー" -X POST -d'{
    "mutation": "createUser",
    "data": {
        "username": "ユーザーメイ" 
    }
}' 作成されたAPI GatewayAPI URL

 

まとめ

AWSは一つ一つのサービスを設定するだけならそんなに難しくはならないけど、必ず複数のサービスを紐づけて使用することになるのでポリシー等の設定がよくわからなくなりがちになります。

今回調べたことは直接何かで使えるかは分かりませんが、AWSでよく使われているであろうLambdaとAPI Gatewayを勉強できたのは面白かったです。

よく使うサービスは少しずつできるようになっていきたいです。

 

参考ページ一覧

Amplify公式ドキュメント

https://docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/ios

https://docs.amplify.aws/lib/auth/getting-started/q/platform/ios

 

AppSyncの複数認証

Cognito認証されているAppSyncをIAM認証で後ろから叩く - Qiita

 

AppSyncのGraphQLを実行するLambda実装

AlexaのPersistent AttributesをAppSyncからGraphQLで取得する | WP Kyoto

node_modulesをLambdaレイヤーにアップロードしてライブラリを使用する - Qiita