はじめに
Visual StudioでAWS Serverless ApplciationでサーバレスのWebアプリケーションを作成すると、標準ではAPI Gateway REST APIを利用するように構成されます。この記事では最新のAPI Gateway HTTP APIを利用する方法について説明します。
API Gateway HTTP APIはAPI Gateway REST APIよりも高速で、低価格、高機能なAPI Gatewayのバージョンです。詳しくは下記のドキュメントを参照してください。
プロジェクトの種類
AWS Toolkit for Visual StudioをインストールするとAWS Serverless Applciation(with Tests)というプロジェクトテンプレートが有効になり、下記のBlueprintからAWSのサーバレスアプリケーションを作成することができます。

.NET Core 3.1を利用する場合はASP.NET Core Web APIかASP.NET Core Web Appのどちらかを選択します。Startup.csの違いだけなのでどちらを選択しても問題ありません。
.NET 5を利用する場合は、現時点ではLambdaが.NET 5をネイティブにサポートしていないためASP.NET Core 5(Container Image)を選択する必要があります。
プログラムのエントリーポイント
プロジェクト作成すると、下図のような構成でプロジェクトが作成されます。

ASP.NET CoreではProgram.csがエントリーポイントになりますが、AWS Serverless ApplicationのASP.NET Coreプロジェクトでは、LambdaEntryPoint.csかLocalEntryPoint.csのどちらかがエントリーポイントになります。。
LocalEntryPoint.csはローカル環境でのデバック実行時に呼びだされるエントリーポイントです。ASP.NET CoreのProgram.csに記載されている内容と変わらないので特に迷うところはないと思います。対してLambdaEntryPoint.csはLambdaのコールドスタート時に呼びだされるエントリーポイントです。
下記はLambdaEntryPoint.csに記載されているコメントの抜粋です。LambdaEntryPoint.csはこのコメントにあるように、Lambdaの前段にあるサービスによって継承するクラスを切り替える必要があります。デフォルトではAPI GatewayがAPI Gateway REST APIとして構成されるため、Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunctionが継承されています。
// The base class must be set to match the AWS service invoking the Lambda function. If not Amazon.Lambda.AspNetCoreServer
// will fail to convert the incoming request correctly into a valid ASP.NET Core request.
//
// API Gateway REST API -> Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction
// API Gateway HTTP API payload version 1.0 -> Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction
// API Gateway HTTP API payload version 2.0 -> Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction
// Application Load Balancer -> Amazon.Lambda.AspNetCoreServer.ApplicationLoadBalancerFunction
//
// Note: When using the AWS::Serverless::Function resource with an event type of "HttpApi" then payload version 2.0
// will be the default and you must make Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction the
API Gateway HTTP APIを使うように構成する
API GatewayにAPI Gateway HTTP APIを利用する場合は下記の2つの手順を実施します。
- LambdaEntryPointの継承元クラスを
APIGatewayHttpApiV2ProxyFunctionに変更する - serverless.templateのResourceタイプを
ApiからHttpApiに変更する
まず、継承元クラスをAmazon.Lambda.AspNetCoreServer.APIGatewayProxyFunctionからAmazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunctionに変更します。
namespace AWSServerless8
{
public class LambdaEntryPoint :
// Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction
Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction
{
// ...略...
}
}
続いて、serverless.templateのResourceタイプをApiからHttpApiに変更します。また、${ServerlessRestApi}変数を${ServerlessHttpApi}に変更します。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Transform": "AWS::Serverless-2016-10-31",
"Description": "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.",
"Parameters": {},
"Conditions": {},
"Resources": {
"AspNetCoreFunction": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Handler": "AWSServerless8::AWSServerless8.LambdaEntryPoint::FunctionHandlerAsync",
"Runtime": "dotnetcore3.1",
"CodeUri": "",
"MemorySize": 256,
"Timeout": 30,
"Role": null,
"Policies": [
"AWSLambda_FullAccess"
],
"Events": {
"ProxyResource": {
- "Type": "Api",
+ "Type": "HttpApi",
"Properties": {
"Path": "/{proxy+}",
"Method": "ANY"
}
},
"RootResource": {
- "Type": "Api",
+ "Type": "HttpApi",
"Properties": {
"Path": "/",
"Method": "ANY"
}
}
}
}
}
},
"Outputs": {
"ApiURL": {
"Description": "API endpoint URL for Prod environment",
"Value": {
- "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
+ "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
}
}
}
}
デプロイすると、API Gatewayの設定画面でプロトコルがRESTからHTTPになっているのが確認できます。
APIを呼び出すとちゃんと結果が返ってきますね。
$ curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/values
["value1","value2"]
API GatewayにOIDCの認証を委譲する
ASP.NET Coreでサーバレスアプリケーションを組み込む場合は、ASP.NET Core側でやってもそんなに手間ではないですが、せっかくなのでHTTP APIの機能を使ってOIDCの認証をAPI Gatewayに受け持ってもらいましょう。
IdentityServer 4のデモサーバーを使ってアクセストークンを取得します。
curl -X POST https://demo.identityserver.io/connect/token \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=m2m.short" \
--data-urlencode "client_secret=secret"
このアクセストークンは次のような構成になっているので、API Gatewayではissがhttps://demo.identityserver.ioで、scopeにapi.scope1を持っていることを検証することにしましょう。
{
"nbf": 1628133438,
"exp": 1628133513,
"iss": "https://demo.identityserver.io",
"aud": "api",
"client_id": "m2m.short",
"jti": "1F8BBB5F7E6513B215C15351BF102424",
"iat": 1628133438,
"scope": [
"api",
"api.scope1",
"api.scope2",
"scope2"
]
}
ドキュメントを見ながら設定してみましたが、、、

んー、Visual Studioの拡張機能がAuthorizationScopesを認識してくれないのか、エラーになってしまいます。無理やりデプロイしようとしても、こんなエラーメッセージが表示されデプロイが失敗してしまいました。
Failed to create CloudFormation change set: Transform AWS::Serverless-2016-10-31 failed with: Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [AspNetCoreFunction] is invalid. Event with id [ProxyResource] is invalid. Unable to set Authorizer [OpenIdAuth] on API method [any] for path [/{proxy+}] because the related API does not define any Authorizers.
Failed to publish AWS Serverless application
仕方がないので、API Gatewayを直接操作していきましょう。
オーソライザーを作成してアタッチをクリックして、

次の値を設定して作成ボタンをクリックします。
| 項目 | 設定値 | 備考 |
|---|---|---|
| オーソライザーのタイプ | JWT | |
| 名前 | OpenIdAuth | 見出しなので任意の名前を設定 |
| ID ソース | $request.header.Authorization | |
| 発行者 URL | https://demo.identityserver.io | issクレームの値 |
| 対象者 | api | audクレームかclient_idクレームの値 |
認可ページで/{proxy+}にJWT認証というマークがついたのを確認できます。続けて、認可スコープにapi.scope1を設定して保存します。

この状態でAPIを呼び出すとUnauthorizedで拒否されるようになりました。
$ curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/values
{"message":"Unauthorized"}
改めてIdentityServer 4 のデモサーバーからアクセストークンを発行して
$ curl -X POST https://demo.identityserver.io/connect/token \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=m2m.short" \
--data-urlencode "client_secret=secret"
{"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg5QkVCRDE0MkI4M0U4RUEyNjQ3Q0U2MkNGRTQxMENFIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MjgxMzg0MzUsImV4cCI6MTYyODEzODUxMCwiaXNzIjoiaHR0cHM6Ly9kZW1vLmlkZW50aXR5c2VydmVyLmlvIiwiYXVkIjoiYXBpIiwiY2xpZW50X2lkIjoibTJtLnNob3J0IiwianRpIjoiRTkzRjBCN0ZDNUIwRTRCOTc2OTQ5N0E1NUQ5QkYyRDQiLCJpYXQiOjE2MjgxMzg0MzUsInNjb3BlIjpbImFwaSIsImFwaS5zY29wZTEiLCJhcGkuc2NvcGUyIiwic2NvcGUyIl19.R7ki8Ww57wRhO_z39VlLPfAJmXfAL6BAvYEIPsmj82QukCNk7jC67Ge-NvUOoYx7XNWbWyD6WQodghXxlHUSvZRmwyqBP15v6UPWd3lRPhNiof9PyD-jvzyp51248DFBmcCJ1KFkvOZxeZTDe3M94wMEk-OHIytem9m_1AYLGLqLpmRq4T0CXkzu77yGgQ9TX0jroYBjdJIFpX86JDGc7vI7RS-HvL_5witlYC3KceUDhR-Uv-IImDozChufS0pln7ZUuAFIcKX0BwNeneiaYQBF_a_DOmjqSgsKscczZ8rmvujJv4We1ikOWXuKn2lR0UOUnLcehqszjI7O5AK8MA","expires_in":75,"token_type":"Bearer","scope":"api api.scope1 api.scope2 scope2"}
返ってきたアクセストークンをauthorizationヘッダーに乗せてAPIを実行するとちゃんと結果が返ってきましたね。
$ curl https://7bp7g5aqwa.execute-api.ap-northeast-1.amazonaws.com/api/values \
--header 'authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg5QkVCRDE0MkI4M0U4RUEyNjQ3Q0U2MkNGRTQxMENFIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MjgxMzg0MzUsImV4cCI6MTYyODEzODUxMCwiaXNzIjoiaHR0cHM6Ly9kZW1vLmlkZW50aXR5c2VydmVyLmlvIiwiYXVkIjoiYXBpIiwiY2xpZW50X2lkIjoibTJtLnNob3J0IiwianRpIjoiRTkzRjBCN0ZDNUIwRTRCOTc2OTQ5N0E1NUQ5QkYyRDQiLCJpYXQiOjE2MjgxMzg0MzUsInNjb3BlIjpbImFwaSIsImFwaS5zY29wZTEiLCJhcGkuc2NvcGUyIiwic2NvcGUyIl19.R7ki8Ww57wRhO_z39VlLPfAJmXfAL6BAvYEIPsmj82QukCNk7jC67Ge-NvUOoYx7XNWbWyD6WQodghXxlHUSvZRmwyqBP15v6UPWd3lRPhNiof9PyD-jvzyp51248DFBmcCJ1KFkvOZxeZTDe3M94wMEk-OHIytem9m_1AYLGLqLpmRq4T0CXkzu77yGgQ9TX0jroYBjdJIFpX86JDGc7vI7RS-HvL_5witlYC3KceUDhR-Uv-IImDozChufS0pln7ZUuAFIcKX0BwNeneiaYQBF_a_DOmjqSgsKscczZ8rmvujJv4We1ikOWXuKn2lR0UOUnLcehqszjI7O5AK8MA'
["value1","value2"]
まとめ
-
AWS Serverless ApplciationでAPI GatewayにHTTP APIを使いたい場合は、エントリーポイントの継承元をAmazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunctionに変更して、serverless.templateのResourceタイプをHttpApiを変更すれば利用できる - serverless.templateではJWT用のAuthoizerの設定が行えないっぽいので、設定を行う場合はマネジメントコンソールから行う

