API Gateway REST APIのデフォルトのゲートウェイレスポンスをカスタムする方法を試してみた [AWS CDK]

2024.03.11

API Gatewayで構築したREST APIのデフォルトのゲートウェイレスポンスの内容を変更したいシーンが有り、CDKを用いた方法を調査しましたので紹介します。

変更対象のレスポンスはAPIキー不正時のレスポンスです。
デフォルトでは403のステータスコードと、{"message":"Forbidden"}のエラーメッセージを返しますが、もう少し何が不正でエラーとなったかが具体的なメッセージを返したかったのが主な理由です。
その他のカスタムできるレスポンスは以下のドキュメントから確認可能です。

カスタムレスポンス実装前

CDKコード

カスタムレスポンス実装前のCDKコードは以下の通りです。36行目でAPIキーを実装しています。

lib/constructs/api.ts

import {
  aws_lambda as lambda,
  aws_lambda_nodejs as lambdaNodeJs,
  aws_apigateway as apigateway,
  type StackProps,
} from "aws-cdk-lib";
import { Construct } from "constructs";

export class ApiConstruct extends Construct {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id);

    const restApiFunc = new lambdaNodeJs.NodejsFunction(this, "RestApiFunc", {
      runtime: lambda.Runtime.NODEJS_20_X,
      architecture: lambda.Architecture.ARM_64,
      entry: "src/lambda/handlers/rest-api-router.ts",
      tracing: lambda.Tracing.ACTIVE,
    });

    const restApi = new apigateway.LambdaRestApi(
      this,
      "TestRestApi-CustomResponse",
      {
        handler: restApiFunc,
        deployOptions: {
          stageName: "v1",
          tracingEnabled: true,
        },
        defaultMethodOptions: { apiKeyRequired: true },
      },
    );

    const plan = restApi.addUsagePlan("UsagePlan", { name: `ApiUsagePlan` });
    plan.addApiStage({ stage: restApi.deploymentStage });
    plan.addApiKey(
      restApi.addApiKey("TestApiKey", { apiKeyName: "TestApiKey" }),
    );
  }
}

レスポンスの確認

CDKでリソースをデプロイ後、APIキーが不正なリクエストを送信してみます。 すると以下のデフォルトレスポンスが返ってきます。

$ curl -i -H 'X-API-Key:<不正なAPIキー>' -X GET https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/v1/hello

HTTP/2 403  # ステータスコードが403
content-type: application/json
content-length: 23
date: Sun, 10 Mar 2024 13:03:11 GMT
x-amzn-requestid: 30fc6a44-1573-4d31-901b-07a227d34a79
x-amzn-errortype: ForbiddenException
x-amz-apigw-id: UaheZFigtjMEVbA=
x-amzn-trace-id: Root=1-65edaf8f-5dc789717a0dbf01596c95f3
x-cache: Error from cloudfront
via: 1.1 e03d10c30b7aad9ba18e946bacd5ad2e.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT20-C4
x-amz-cf-id: vEpGUOkV-YU7sgSsldQYwwVthMehOYq5NNUzmVD9pOGEoKa9t9mtzw==

{"message":"Forbidden"} # デフォルトのエラーメッセージ

コンソールでレスポンスを確認

マネジメントコンソールでデフォルトのレスポンスを確認します。
対象のAPIを選択すると現れるメニューのゲートウェイのレスポンスをクリックすると確認画面に遷移できます。
対象のレスポンスのResponse typeINVALID_API_KEYです。
CDKでカスタム設定する場合Response typeを指定しますので、コンソールを英語表示にして対象のtypeを控えておきます。

INVALID_API_KEYのレスポンスのステータスコードが403で、ボディは$context.error.messageStringとなっており、API Gatewayが内部で使用するコンテクスト内のメッセージが返されていることがわかります。

カスタムレスポンスを実装

それでは、カスタムレスポンスを実装してみます。

レスポンスのカスタム内容

変更後のレスポンスの内容は前項のレスポンスのステータスコードを401、エラーメッセージを以下のようにします。

{
  "error": {
      "type": "unauthorized",
      "message": "invalid api key"
  }
}

CDKコード

CDKでカスタムレスポンスを実装するには、39行目以降に追加したaddGatewayResponseメソッドを使用します。
代2引数のプロパティで以下のように設定します。

  • type : レスポンスのタイプを指定します。INVALID_API_KEYを指定します。
  • statusCode : ステータスコードを指定します。401を指定します。
  • templates : レスポンスのボディを指定します。ここに先程のエラーレスポンスを記載します。

これらを反映したコードは以下の通りです。

lib/constructs/api.ts

import {
  aws_lambda as lambda,
  aws_lambda_nodejs as lambdaNodeJs,
  aws_apigateway as apigateway,
  type StackProps,
} from "aws-cdk-lib";
import { Construct } from "constructs";

export class ApiConstruct extends Construct {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id);

    const restApiFunc = new lambdaNodeJs.NodejsFunction(this, "RestApiFunc", {
      runtime: lambda.Runtime.NODEJS_20_X,
      architecture: lambda.Architecture.ARM_64,
      entry: "src/lambda/handlers/rest-api-router.ts",
      tracing: lambda.Tracing.ACTIVE,
    });

    const restApi = new apigateway.LambdaRestApi(
      this,
      "TestRestApi-CustomResponse",
      {
        handler: restApiFunc,
        deployOptions: {
          stageName: "v1",
          tracingEnabled: true,
        },
        defaultMethodOptions: { apiKeyRequired: true },
      },
    );

    const plan = restApi.addUsagePlan("UsagePlan", { name: `ApiUsagePlan` });
    plan.addApiStage({ stage: restApi.deploymentStage });
    plan.addApiKey(
      restApi.addApiKey("TestApiKey", { apiKeyName: "TestApiKey" }),
    );

    restApi.addGatewayResponse("InvalidApiKeyResponse", {
      type: apigateway.ResponseType.INVALID_API_KEY,
      statusCode: "401",
      templates: {
        "application/json": JSON.stringify({
          error: {
            type: "unauthorized",
            message: "invalid api key",
          },
        }),
      },
    });
  }
}

スポンスの確認

カスタムレスポンスの追加実装をデプロイ後、APIキーが不正なリクエストを送信してみます。
以下のレスポンスが返ってきます。

$ curl -i -H 'X-API-Key:<不正なAPIキー>' -X GET https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/v1/hello

HTTP/2 401 # ステータスコードが401
content-type: application/json
content-length: 61
date: Sun, 10 Mar 2024 15:31:00 GMT
x-amzn-requestid: 8abfd085-710b-4ce3-965e-14756e8bfff0
x-amzn-errortype: ForbiddenException
x-amz-apigw-id: Ua3ING8PNjMElvw=
x-amzn-trace-id: Root=1-65edd234-2f00b91e50f4070e21ce0d0e
x-cache: Error from cloudfront
via: 1.1 b975fe7c5ea4d03e3d0250a217f79d38.cloudfront.net (CloudFront)
x-amz-cf-pop: KIX56-C1
x-amz-cf-id: QnnVb-4qQ4ktEitqyKY4vZKPTYPERyH9izDTk9gSqqCOrPx3u3V-9Q==

{"error":{"type":"unauthorized","message":"invalid api key"}} # カスタムしたエラーメッセージ

指定した通りのステータスコードとエラーメッセージが返ってきていることが確認できます。

コンソール側も確認

マネジメントコンソールでカスタムレスポンスが反映されているか確認します。
先ほどと同じ手順でゲートウェイレスポンスを確認します。
こちらも同様に指定した通り、ステータスコードが401で、ボディがカスタムしたエラーメッセージになっていることが確認できます。

最後に

CDKでAPI Gatewayのゲートウェイレスポンスをカスタムする方法を紹介しました。
要件に応じてデフォルトのレスポンスを変更する必要がある場合、カスタムレスポンスを実装して任意のステータスコードやエラーメッセージを返すことができるので、ぜひ活用してみてください。