こんにちは、広野です。
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-universal 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-universal'; 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 を出してくれることを待っております・・・。
本記事が皆様のお役に立てれば幸いです。