はじめに
この記事は、AmplifyのFunctionから、IAM認証を使ってAPIを操作する方法を共有するためのものです。
対象とするユーザー
- AWSを使ったことがある
- AmplifyのGetting startedを完了した
- Amplifyに含まれている個別のサービスを使ったことがない
対象とする環境
この記事では、JavaScriptベースのWebアプリケーションを対象とします。モバイルアプリケーションは対象としません。
必須となるnpmモジュールは以下の通りです。
▼package.json
"dependencies": {
"aws-amplify": "^3.3.6"
}
パッケージのバージョンを確認してください
この記事では2020/12/04時点でのAmplifyを前提にしています。Amplifyは活発に開発が行われているため、導入手順が大きく変更されている場合があります。記事を読む前に、お手元の環境を確認してください。
先に結論だけ
- APIの
追加の認証プロバイダー
にIAMを追加する - スキーマの
@auth
ディレクティブで、IAM認証を追加する - Functionに、APIへのアクセス許可を追加する
- Lambda関数内からAPIへリクエストするときに、リクエストヘッダーにIAMロールで署名をつける
という手順でAmplifyのFunctionからAPIをIAM認証で呼び出します。
Amplifyの構成
まず、今回の記事で対象となる各サービスを解説します。
Amplify
Amplifyとは、モバイル/Webアプリケーションの開発に必要なAWSサービスのパッケージです。アプリケーションに必要となるAPI/ユーザー認証/ホスティング/ユーザー分析といった機能がひとまとめにされています。
Amplifyの各サービスはAWS AppSyncによってアクセスが制御されます。開発者は各サービスのユーザー登録/認証を意識する必要はありません。
Amplify Auth
Amplify Authはユーザー認証と、その認証に基づいたアクセス制御のためのサービスです。
AuthサービスはAWS AppSyncとAWS Cognitoの連携で実現されています。Cognitoがユーザー登録DBの役割を担い、AppSyncがCognitoの登録情報に基づきアクセス制御を担当します。
Amplify Function
Amplify Functionはサーバーレスでプログラムを実行するサービスです。AWS EC2のように仮想マシンが立ち上がっているわけではありません。何らかのトリガーによって呼び出された時だけ、事前に設定したプログラムが動きます。
FunctionサービスはAWS Lambdaで構成されています。
Amplifyは複数のサービスから構成されており、APIのアクセス制御機能はAppSyncのセキュリティ機能で実現されています。
Amplify FunctionへのIAM認証の追加
Amplifyのサービス構成を踏まえて、FunctionへのIAM認証を追加します。
AppSyncに認証プロバイダーを追加する
まずはAppSyncに認証プロバイダーとしてIAMを追加します。amplify update api
コマンドでAPIに認証プロバイダーを追加します。
% amplify update api
? Please select from one of the below mentioned services: GraphQL
? Select from the options below Update auth settings
? Choose the default authorization type for the API API key
? Configure additional auth types? Yes
? Choose the additional authorization types you want to configure for the API (Press <space> to select, <a> to toggle all, <i> to invert selection)
◯ Amazon Cognito User Pool
❯◉ IAM
^^^^^^^
◯ OpenID Connect
ここで認証タイプを追加→IAMと選択します。
❗下線で強調した部分は複数選択可能なチェックボックスです。スペースキーで選択、リターンで決定という操作方法です。なにもチェックを行わないままリターンを押すと操作がキャンセルされたと解釈して処理が進みます。ご注意ください。
GraphQL schema compiled successfully.
Edit your schema at <プロジェクト名>/amplify/backend/api/<API名>/schema.graphql or place .graphql files in a directory at <プロジェクト名>/amplify/backend/api/<API名>/schema
Successfully updated resource
成功メッセージのあとに、amplify push
します。
AWSコンソールからAppSyncにアクセスし、「追加の認証プロバイダー」にIAMが登録されていることを確認します。
@auth
ディレクティブで、APIへIAM認証でのアクセスを許可する
AppSyncのセキュリティ機能とAmplify APIがどのように関係するかを確認しましょう。まずAPIのスキーマに@auth
ディレクティブのないモデルを追加してみます。
▼amplify/backend/api/API名/schema.graphql
type Todo @model{
id: ID!
url: String
}
このスキーマをamplify push
コマンドでビルドすると、以下のようなAppSync用のスキーマがbuildディレクトリに生成されます。
▼amplify/backend/api/API名/build/schema.graphql
type Todo {
id: ID!
url: String
createdAt: AWSDateTime!
updatedAt: AWSDateTime!
}
この状態では認証用ディレクティブがないため、AppSyncはデフォルト認証形式でのアクセスを許可します。AmplifyのGetting startedの手順では、デフォルトの認証形式にAPI Key
を指定しています。したがってIAM認証ではアクセスできません。
次にAPIのスキーマに@authディレクティブを追加します。
▼amplify/backend/api/API名/schema.graphql
type Todo
@model
@auth(rules: [
{ allow: private, provider: userPools},
{ allow: private, provider: iam }
]){
id: ID!
url: String
}
このスキーマをamplify push
コマンドでビルドすると、AppSyncのスキーマに@aws_aim
ディレクティブが追加されます。
▼amplify/backend/api/API名/build/schema.graphql
type Todo @aws_iam @aws_cognito_user_pools {
id: ID!
url: String
createdAt: AWSDateTime!
updatedAt: AWSDateTime!
}
これでAPIの特定のテーブルにIAM認証でのアクセスが許可されました。
FunctionにAPIへのアクセスを許可する
FunctionにAPIへのアクセス許可を追加します。amplify update function
コマンドを実行します。
% amplify update function
? Select which capability you want to update: Lambda function (serverless function)
? Select the Lambda function you want to update <function名>
General information
| Name: <function名>
| Runtime: nodejs
Lambda layers
- Not configured
? Which setting do you want to update? Resource access permissions
? Select the category (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ api
◯ auth
◯ analytics
◯ function
◯ storage
Api category has a resource called <API名>
? Select the operations you want to permit for <API名> (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ create
◉ read
◉ update
◉ delete
Functionを選択すると、どの設定を修正するかを聞かれますのでResource access permissions
(リソースのアクセス権限)を選択します。次にAPI
を選択し、新規/読み/書き/削除の権限を追加します。
設定を変更するとfunction-parameters.json
に権限が追記されます。
▼amplify/backend/function/Function名/function-parameters.json
{
"permissions": {
"api": {
"<API名>": [
"create",
"read",
"update",
"delete"
]
}
"lambdaLayers": []
}
この権限はFunction名-cloudformation-template.json
に展開されます。
▼amplify/backend/function/Function名/Function名-cloudformation-template.json
"LambdaExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"IAMロール名",
^^^^^^^^^^^^
{
"Fn::Join": [
"",
[
"IAMロール名",
"-",
{
"Ref": "env"
}
]
]
}
]
},
amplify push
コマンドを実行すると、この権限が上記IAMロールにインラインポリシーとして展開されます。
Lambda関数内からAPIへリクエストするときに、ヘッダーにIAMロールで署名をつける
最後にAmplify FunctionのLambda関数を実装します。公式チュートリアルのSigning a request from Lambdaを参考に、リクエストヘッダーにIAMで署名します。
/* Amplify Params - DO NOT EDIT
API_<API名>_GRAPHQLAPIENDPOINTOUTPUT
API_<API名>_GRAPHQLAPIIDOUTPUT
ENV
REGION
Amplify Params - DO NOT EDIT */
const https = require("https");
const AWS = require("aws-sdk");
const urlParse = require("url").URL;
const appsyncUrl = process.env.API_<API名>_GRAPHQLAPIENDPOINTOUTPUT;
const region = process.env.REGION;
const endpoint = new urlParse(appsyncUrl).hostname.toString();
const ListTodos = `
query ListTodos(
$filter: ModelPublicInfoFilterInput
$limit: Int
$nextToken: String
) {
listTodos(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
url
createdAt
updatedAt
}
nextToken
}
}
`;
exports.handler = async (event) => {
/**
* AppSyncのエンドポイントを宛先としたリクエストを組み立てる
*/
const req = new AWS.HttpRequest(appsyncUrl, region);
req.method = "POST";
req.path = "/graphql";
req.headers.host = endpoint;
req.headers["Content-Type"] = "application/json";
req.body = JSON.stringify({
query: ListTodos,
});
/**
* ここでリクエストヘッダーに署名を追加する
*/
const signer = new AWS.Signers.V4(req, "appsync", true);
signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
const data = await new Promise((resolve, reject) => {
const httpRequest = https.request({ ...req, host: endpoint }, (result) => {
result.on("data", (data) => {
resolve(JSON.parse(data.toString()));
});
});
httpRequest.write(req.body);
httpRequest.end();
});
return {
statusCode: 200,
body: data,
};
};
これでLambda関数内からTodoテーブルのリストを取得できました。
個人的な感想
Amplifyの導入はとても簡単です。データベースと認証機能をもったWebアプリケーションを数十分程度でデプロイできます。
しかしGetting startedから一歩進んだ機能を実装しようとすると、Amplify内の個別サービスの知識が必要になります。Amplifyのどの機能が、AWSのどのサービスに対応しているかを把握すると調査が楽になります。
参考記事
AWS Lambda から AppSync の API を ぶん殴る
以上、ありがとうございました。