こんにちは、広野です。
AWS Lambda 関数 URL がサポートしているレスポンスストリーミングを使用したくて、React アプリから Amazon Cognito ID プール (フェデレーテッドアイデンティティ) の一時的なクレデンシャルを取得し IAM 認証付きの AWS Lambda 関数 URL を呼び出せるようにしようとしたところ、激ハマリしました。
やりたいことは↓コレなんですが、一時的なクレデンシャルを取得して以降の Lambda 関数 URL 呼出に関する AWS 公式情報が具体的なものがなく、ネット上の有志の方のブログ情報を見ても環境の違いやモジュールのバージョン違いのためそのままでは動かず、苦労しました。
以降、動いたコードなどを紹介します。
あらためて、やりたいこと
- React アプリに Amazon Cognito でユーザ認証する。
- 認証済みユーザに、AWS Lambda 関数 URL を呼び出せる権限を付与する。(AWS IAM ロールによる)
- React アプリから、AWS Lambda 関数 URL を呼び出す。
これにより、アプリの認証済みユーザだけが AWS Lambda 関数 URL を呼び出せることになるので、セキュアな仕組みを構築できます。AWS Lambda 関数 URL は CORS もサポートしていますし、アクセスが少なく重要度の低いビジネスロジックであれば API Gateway は不要になります。
(余談) 関数 URL は Amazon API Gateway と違って IPv6 もサポートしています。Amazon CloudWatch Logs に残した AWS Lambda 関数のログを見ると、アクセス元デバイスの IPv6 アドレスが残っていました。AWS WAF はアタッチできないのと、スロットリングとかはできないのでそういう要件が必要になると Amazon API Gateway と統合しないとです。
参考までに、同様の構成で「Amazon Cognito 認証済みユーザのみが Amazon S3 バケットに書き込める」構成を以下ブログに書いておりましたが、このときは AWS が Amazon S3 に書き込むための SDK を用意してくれていたので簡単でした。
Amazon Cognito ID プールから払い出される IAM ロールに AWS Lambda 関数 URL を呼び出す権限をどのように付けるかは、以下に書いてあるように、”Action”: “lambda:InvokeFunctionUrl” を許可してあげれば OK なので簡単です。
続いて AWS Lambda 関数 URL を呼び出す情報は以下に書いてあるんですが、「いや、聞きたいのはそんなことじゃなくてね・・・もちょっと具体的なコードを教えてくれない?」って思う内容でして。
ドキュメントを読んで何がわからないかと言うと。
で、ネット上をめっちゃ調べて、何とか動くようになった React コードを紹介します。(前置き長っ)
動いた React コード
執筆時点の React モジュール環境は以下です。今後、バージョンアップ等で動かなくなる可能性大です。
- react 18.2.0
- aws-amplify 6.0.12
- @smithy/protocol-http 3.0.12
- @smithy/signature-v4 2.0.19
- @aws-crypto/sha256-browser 5.2.0
ちなみに本記事ではアプリ稼働環境に AWS Amplify Console は使っていませんが、Amplify 関連モジュールはあくまでもモジュールなので使えます。
import { fetchAuthSession } from 'aws-amplify/auth';
import { HttpRequest } from '@smithy/protocol-http';
import { Sha256 } from '@aws-crypto/sha256-browser';
import { SignatureV4 } from '@smithy/signature-v4';
const sample = async () => {
//Lambda関数URLを指定
const apiUrl = new URL("https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws");
//Cognito ID プールから一時的なクレデンシャル(IAMアクセスキー等)を取得
const { credentials } = await fetchAuthSession();
//このあたりからが、SigV4 署名をするコード。以下のパラメータは絶対必須。
const signer = new SignatureV4({
service: 'lambda',
region: 'ap-northeast-1',
credentials: {
accessKeyId: credentials.accessKeyId,
secretAccessKey: credentials.secretAccessKey,
sessionToken: credentials.sessionToken
},
sha256: Sha256
});
//Lambda関数に POST したいパラメータ
const body = { test: 'test' };
//以下も変更不可。POSTの場合bodyを含めないとAWS側で署名をverifyしたときにエラーになる。
const httpRequest = new HttpRequest({
headers: {
'Content-Type': 'application/json',
'Host': apiUrl.hostname
},
hostname: apiUrl.hostname,
method: 'POST',
protocol: 'https:',
path: apiUrl.pathname,
body: JSON.stringify(body)
});
const signedRequest = await signer.sign(httpRequest);
//fetchでLambda関数URLを呼び出す。改めてbodyに加え、署名済みのヘッダー情報を付加する。
const res = await window.fetch(
"https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws",
{
method: 'POST',
body: JSON.stringify(body),
headers: signedRequest.headers
}
);
};
前提として、App.js 等に Amplify.configure で Amazon Cognito ユーザープールと ID プールの情報を定義できていないと、fetchAuthSession が機能しません。
AWS Lambda のストリームレスポンスを使用する場合は fetch でないと動作しないようです。axios だと header に host を入れるな、というエラーが出てしまいます。
紹介したコードには固定値が入ってしまっているので適宜修正ください。
まとめ
いかがでしたでしょうか。
AWS Lambda 関数 URL を使えば Amazon API Gateway はいらなくなるぞー、と息巻いていたのですが、Amazon API Gateway の Cognito オーソライザーと同等の認証をしようとすると やたらめんどくさい w。AWS がそれ用の SDK を出してくれることを待っております・・・。
本記事が皆様のお役に立てれば幸いです。