Instagram API を AWS API Gateway でWrappingして、node環境からCognitoを使って叩く方法

S3、Cloudfrontにてwebサイトを公開してますが、jsからSNS(ここではInstagram)のAPIを使用する際、気になるのがAccess token。
Access token をパラメータに含みたくないので、AWSのAPI gatewayでラッピングして、使用するようにしました。
更にそのAPIをAWS_IAM認証制限し、Cognitoを使って node 環境から叩く方法についてメモしておこうと思います。

各種必要なAPIの準備

僕は今回InstagramのAPIを叩きたかったので、下記を参考にdeveloper登録をして、access tokenを取得してAPIを叩けるようにしました。
Instagram APIを使ってWebページに表示する
Instagram Developer

API Gatewayの設定

処理の流れとしては下記です。

  1. API Gateway を叩く
  2. API GatewayからLambda関数が呼ばれる
  3. Labda関数が、必要なパラメータ等の処理をして、Instagram APIを叩く
  4. レスポンス返却

それではAPI Gatewayを設定していきます。

AWSコンソールにログイン -> API Gatewayへ -> APIの作成をクリック

API作成

リソースの作成

API自体が作成されたので、次にリソースを作成します。
アクション -> リソースを作成 を選択します。
API作成3

  • リソース名
  • API Gateway CORSを有効にするにチェック

メソッドの作成

作成されたリソースを選択した状態で、アクション -> メソッドの作成を選択します。
API作成4
セレクトボックからGETを選択し、チェックマークをクリック

ただAPIを叩くラッピングするだけなら、ここで統合タイプをHTTPを選択し、エンドポイントにそのAPIを指定すればOKですが、ただ、client から渡ってきた Query String を endpoint に渡す 際、key は変更できるものの、value を変えたり、特定の key を静的に(=固定で)指定といったことはできないようですので、今回はLambda関数を使用します。
API作成5
今回はAccess tokenを静的に渡したいので、lambda関数を使用します
API Gateway + Lambda で固定のパラメータを付加して HTTP Proxy する

クエリパラメータの設定

今回はinstagram api で投稿データを数軒取得しましたが、全件取得しても無駄なので、必要な件数のみ取得しようと思います。Instagra api 側で countというクエリパラメータがあるので、これをAPI gatewayで設定したAPIにも付加できるように設定します。
API作成6
GETメソッドを選択状態にし、「メソッドリクエストから」クエリ文字列の追加から、クエリ名を入力します。


次に「統合リクエスト」から一番下本文マッピングテンプレートから、追加し、content-typeを「application/json」に設定し下記のように設定します。
API作成65
※、ダブルクォートで囲わないとうまく行きません。

ここまでで、一旦API Gatewayの設定は終わりです。後に、deployとアクセスの制限の設定をします。
次にLambda関数を準備します。

Lambda関数の設定

関数の作成

AWSコンソールより、Lambdaを選択し、関数の作成をクリック
Lambda

トリガーの設定

トリガーを追加します。
Lambda2

関数のコード作成

今回はnode.jsを使います。
local環境にて、

$ yarn init

もしくは

$ npm init

し、projectを作成します。

必要なパッケージを入れます。

$ yarn add requrest 

もしくは

$ npm install request --save

jsファイルを作成します。


index.js

var request = require('request');
exports.handler = function(event, context) {
    var params = { count:event.count, access_token: process.env.ACCESS_TOKEN}; 

    request.get({url:process.env.URL, qs: params}, function(err, res, body) {
      if (!err && res.statusCode == 200) {
        context.done(null, body);
      } else {
        console.log('error: '+ res.statusCode);
      }
    });
};
  • process.envは、後ほどLambda 側で設定します。
  • 単純に、requestを使って、APIを叩く処理です。
  • event.countは先程API Gatewayで設定した、クエリパラメータになります。

次にこれら(index.jsと作成されたnode_modulesの2つ)をLambdaにアップロードするために、zipで圧縮します。

アップロードします。
Lambda3

環境変数を設定

コード中のprocess.envの値を設定します。
Lambda4

これでLambdaの設定は以上になります。

API Gatewayをdeployする

デプロイ

AWSコンソールにログイン -> 再びAPI Gatewayへ -> APIの作成をクリック
先程作成した、APIを選択します。

API ルートを選択し、アクションから、APIのデプロイを選択
API作成7

エンドポイントの確認

左メニューのステージを選択する
API作成8

ここまでで、APIのラッピング作業は終わりです。実際に表示されているURLを叩いて結果が帰ってくればOKです。
設定した、クエリパラメータ(count)も指定してみましょう。

これでOKですが、APIのURLがバレたら誰でも叩けてしまうのはちょっと嫌なので(jsから使うため)、Cognitoを使って制限します。

API Gatewayに認証をつける

AWSコンソールにログイン -> 再びAPI Gatewayへ -> APIの作成をクリック
先程作成した、APIを選択します。
API作成9
GET を選択し、認証をAWS_IAMに設定します。

これで先程と同じようにデプロイし、エンドポイントに表示されているURLをたていてもデータを取得できないことを確認します。

ARNのメモ

API Gatewayの画面で、ARNをメモしておきます。
API作成10


Cognitoの設定

プールIDの作成

AWSコンソールにログイン -> Cognitoへ -> フェデレーテッドアイデンティティの管理を選択 -> 新しい ID プールの作成
Cognito
プールIDを作成します


ポリシーの設定

Cognito2
ポリシードキュメントを下記のように設定します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "execute-api:Invoke"          
      ],
      "Resource": [
        "arn:aws:execute-api:xx-xxxx-xx:xxxxxxxxxxxx:xxxxxxxxxx/*/GET/instagram"
      ]
    }
  ]
}
  • Resource部分は、execute-api:リージョン:arn-idになります。先程メモった内容です。

jsコードを取得

Cognito3

これでcognitoの設定はOKです。


Node 環境から実際に使ってみる

AWS API Gateway は便利なサービスですが、Node.js から使おうとすると、自動で生成される JavaScript のSDKがブラウザ用しか用意されていなかったり、エンドポイントごとにメソッドが自動で生成されていて、APIのエンドポイントを変更するごとにSDKを生成し直さなければならなかったりと不便だったため、Node.js から API Gateway を簡単に使えるパッケージを作ってくれた方がいらっしゃいましたので、こちらを使用させていただきました。ありがとうございます。
Node.js から API Gateway を簡単に使えるパッケージ作りました

準備

$ yarn add aws-sdk
$ yarn add aws-api-gateway-client

もしくは

$ npm install add aws-sdk
$ npm install aws-api-gateway-client

※parcelを使っていますが、babelモジュール等の依存があるので、このパッケージで使っているモジュールを入れます

.babelrc

{
"presets": [
  "stage-0",
  "@ava/stage-4",
  "babel-preset-es2015",
  "babel-preset-es2016",
  "babel-preset-es2017",
],
"plugins": ["transform-es5-property-mutators"]

}

上記が必要なものですので、すべてnpm or yarnで入れます。各moduleについてはググれば出てきますのでお願いします。
2018/03 これは、babelrcに入れなくても、aws-api-gateway-clientに上記が必要なだけですので、install しておくだけでOKです。

$ yarn add --dev babel-plugin-transform-es5-property-mutators @ava/babel-preset-stage-4 babel-preset-stage-0 babel-preset-es2015 babel-preset-es2016 babel-preset-es2017

OR

$ npm install --save-dev babel-plugin-transform-es5-property-mutators @ava/babel-preset-stage-4 babel-preset-stage-0 babel-preset-es2015 babel-preset-es2016 babel-preset-es2017


コード

  import AWS from 'aws-sdk'

  let apigClientFactory = require('aws-api-gateway-client').default

  // Amazon Cognito 
  AWS.config.region = 'xxxxxxxxxxx'
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
      IdentityPoolId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  })

  AWS.config.credentials.get(function(err){
    if(!err){
      const apigClient = apigClientFactory.newClient({
        accessKey: AWS.config.credentials.accessKeyId,
        secretKey: AWS.config.credentials.secretAccessKey,
        sessionToken: AWS.config.credentials.sessionToken,
        region: 'xxxxxxxx',
        invokeUrl: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
      });
      let params = {
        //This is where any header, path, or querystring request params go. The key is the parameter named as defined in the API
      };
      // Template syntax follows url-template https://www.npmjs.com/package/url-template
      let pathTemplate = 設定したAPIのパス
      let method = 'GET'
      let additionalParams = {
      //If there are any unmodeled query parameters or headers that need to be sent with the request you can add them here
        queryParams: {
          count: 必要な件数,
        }
      };
      let body = {
        //This is where you define the body of the request
      };

      let items = []
      apigClient.invokeApi(params, pathTemplate, method, additionalParams, body)
      .then(function(result){
        // 成功処理
        });
      }).catch( function(result){
        //This is where you would put an error callback
        // エラー処理
      });
    }else{
      console.log(err)
    }
  })
})

以上です。

まとめ

これで安全にAPIを叩くことができるようになりました。
意外とnode環境からAPI gatewayを叩くすべがあまりなく、たまたま作ってくださっていた方がいらっしゃいましたので、助かりました。
ありがとうございました。