【初心者向け】Vue+API Gateway+LambdaでサーバレスWebアプリを作った際に遭遇した問題


概要

「AWSを使って、サーバレスアプリを作りたい!」と思い実践したのですが、Lambda関連で結構な壁があったので備忘がてら残します。

ハマりポイントは以下3つ。

  • LambdaのResponseは特定のJSON形式でないとNGな話
  • API連携の際に、CROS Policyでエラーになった話
  • APIに認証を噛ませる際は、accessTokenではなくidTokenを使用する話

LambdaのResponseは特定のJSON形式でないとNGな話

今回はフロントサイドはvueで作成、そこからAPIGatewayを通してLambdaのFunctionを呼び出すという構成でした。
このFunctionの返却の仕方は何でもよい訳ではなくJSONの形式が決まっています。

参考:https://aws.amazon.com/jp/premiumsupport/knowledge-center/malformed-502-api-gateway/

以下は、Lambdaを通してTwitterに投稿する関数の例です


exports.handler = (event, context, callback) => {
    let date = new Date();
    twitter_client.post('statuses/update', {status: 'テスト投稿。from lambda ' + date},  function(error, tweet, response){
        if(error) {
            //エラーハンドリング
        }
        callback(null, JSON.stringify(response));
    });
};

関数単体はテスト時に動いても、上記のような正しい形式でないと、実際のフロントからの実行時には500エラーを返すので注意。

API連携の際に、CROS Policyでエラーになった話 

「JSONを返す必要があるということはわかったぜ!これでオッケーや!」と思ったら、まだ駄目でした。
今度はフロント側でエラーになりました。。。ブラウザのF12コンソールでエラーの内容を確認すると
「Access to XMLHttpRequest at 'http:/~' from origin 'http://localhost:8080' has been blocked by CORS policy:
…」と書いてありました。
あぁーCROSね、なんかAPIGatewayで設定があった気がするなーと思いいろいろググります。

※CROSについては以下の記事が参考になりました。
https://qiita.com/att55/items/2154a8aad8bf1409db2b

結論から言うと、以下のように設定するのが正解でした。
レスポンスのヘッダーに
Access-Control-Allow-Headers (使用するHeadersの値を追加)
Access-Control-Allow-Origin (許可するリソースを追加)
Access-Control-Allow-Credentials (これはなくてもいいかも)
Access-Control-Allow-Methods (必要なメソッドを追加)
を追加します。

lambdaの関数のレスポンスに直接書いているやり方もありましたが、
調べて実際に動かしたところこれでも大丈夫。(ケースバイケースだが多分ほとんどの場合これがベターな気がする)

APIに認証を噛ませる際は、accessTokenではなくidTokenを使用する話

これはハマったというほどではなかったが、少し勘違いがあったので記載。
上記の問題でAPI通信は問題なく通るようになったが、APIには認証をかけたほうがよい。

認証のかけ方もいくつか方法があるが、今回はcognitoユーザプールを使った。
※cognitoを使ったログイン機能を実装していることが前提です。

手順としては
1.APIgatewayのコンソールからAuthorizersを作成
2.使用するAPIに適用する
の2 STEEP(めちゃ簡単)

フロントのコードは以下のような感じで実装

App.vue
import { Auth } from 'aws-amplify'
import axios from 'axios'

let cognitoUser = await Auth.currentAuthenticatedUser()
this.signedIn = true
this.username = cognitoUser.username
let token = cognitoUser.signInUserSession.idToken.jwtToken;
console.log(cognitoUser.signInUserSession);

中略
APIfunction(){
        axios
          .get('APIのURL',
            {
              headers: {
                Authorization: token
              }
            }
          )
          .then(function (response) {
            // handle success
            console.log(response);
            alert("success");
            alert(response);
          })
          .catch(function (error) {
            // handle error
            console.log(error);
            alert("error");
            alert(error);
          })
    }

signInUserSessionの中にはaccessTokenという似たプロパティもあるが、こっちではなくidTokenを使用した点に注意。
※accessTokenでも認証機能を作ることはできるかも知れませんが、詳細は調査しきれず…(だれか知っている人がいたら教えてください…)

まとめ

AWSを使いこなすまでの道のりは遠い…
一方で使いこなすと非常に便利だということも実感できた。前進あるのみ。