LoginSignup
17
10

More than 3 years have passed since last update.

Amplify Functionから、IAM認証を使ってAPIを呼ぶ

Last updated at Posted at 2020-12-11

はじめに

この記事は、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 AppSyncAWS 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 を ぶん殴る

以上、ありがとうございました。

17
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
10