[AWS CDK] API Gateway(REST API)のCORSの動作を確認してみた

2021.08.15

こんにちは、CX事業本部 IoT事業部の若槻です。

今回は、AWS CDKで実装したAPI Gateway(REST API)のCORS(Cross-origin resource sharing)の動作を確認してみました。

REST APIにおけるCORS

基本的に下記ドキュメントに記載の内容です。

Cross-origin resource sharing(CORS)とは、ブラウザで実行されているスクリプトから開始されるクロスオリジンHTTPリクエストを制限するブラウザのセキュリティ機能です。

クロスオリジンリクエストは、シンプルなリクエストシンプルでないリクエストの2種類に分けられます。

以下の条件がすべて該当する場合はシンプルなリクエストとなります。

  • GET、HEAD、およびPOSTのいずれかのメソッドのリクエストである
  • POSTメソッドリクエストの場合、Originヘッダーを含んでいる
  • リクエストのペイロードコンテンツタイプが text/plain、multipart/form-data、または application/x-www-form-urlencoded のいずれかである
  • リクエストにカスタムヘッダーが含まれていない
  • シンプルなリクエストに関する Mozilla CORS のドキュメントに一覧表示されている追加要件。

REST APIのリソースがクロスオリジンのシンプルでないリクエストを受け取る場合はCORSを有効にする必要があります。

確認してみた

シンプルなリクエストの場合(GET)

シンプルなリクエストのCORSの動作確認はGETリクエストで行ってみます。

レスポンスを返すLambdaのコードです。

src/lambda/handlers/index.js

exports.handler = async (event) => {
  const response = {
      statusCode: 200,
      body: JSON.stringify('Hello from Lambda!'),
  };
  return response;
};

LambdaとREST APIをLambdaプロキシ統合で実装するCDKスタックのコードです。

lib/aws-cdk-app-stack.ts

import * as cdk from "@aws-cdk/core";
import * as lambda from "@aws-cdk/aws-lambda";
import * as apigateway from "@aws-cdk/aws-apigateway";

export class AwsCdkAppStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const testFunction = new lambda.Function(this, "TestFunction", {
      code: lambda.Code.fromAsset("src/lambda/handlers"),
      functionName: "testFunction",
      handler: "index.handler",
      runtime: lambda.Runtime.NODEJS_14_X,
      memorySize: 128,
    });

    const restApi = new apigateway.RestApi(this, "RestApi", {
      restApiName: "test",
      deployOptions: {
        stageName: "v1",
      },
    });

    const dataResource = restApi.root.addResource("data");
    dataResource.addMethod(
      "GET",
      new apigateway.LambdaIntegration(testFunction)
    );
  }
}

cdk deployしてリソースをデプロイします。

まず、ブラウザからREST APIのエンドポイントにアクセスすると、Lambdaからのレスポンスを表示させられました。

次に、下記のWebアプリ(React)からAxiosでGETリクエストをして確認してみます。

web/src/App.tsx

import React, { useState, useEffect } from "react";
import "./App.css";
import axios from "axios";

const endpointUri = process.env.REACT_APP_REST_API_ENDPOINT_URI!;

const App: React.FC = () => {
  const [body, setBody] = useState<any>();
  useEffect(() => {
    axios.get(endpointUri).then((res) => {
      setBody(res.data);
    });
  }, []);

  return <div className="App">{body}</div>;
};

export default App;

Webアプリへ接続すると、CORSエラーとなりました。

ドキュメントによるとシンプルなクロスオリジンリクエストの場合はAccess-Control-Allow-Originヘッダーが必要とのことです。(記載にはPOST メソッドリクエストの場合とありますが、ここはGETなどそれ以外のリクエストにも該当するようです)

シンプルなクロスオリジン POST メソッドリクエストの場合、リソースからのレスポンスには Access-Control-Allow-Origin ヘッダーを含める必要があります。ここで、ヘッダーキーの値は '*' (任意のオリジン) に設定されるか、そのリソースへのアクセスが許可されているオリジンに設定されます。

そこでLambdaのコードでレスポンスヘッダーを下記のように追加します。

src/lambda/handlers/index.js

exports.handler = async (event) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify('Hello from Lambda!'),
    headers: {
      "Access-Control-Allow-Origin": "*"
    },
  };
  return response;
};

cdk deployをしてLambdaを修正し、Webアプリに再度接続すると、正常にLambdaからのレスポンスを取得できました。

シンプルでないリクエストの場合(POST)

シンプルでないリクエストのCORSの動作確認はPOSTリクエストで行ってみます。

下記のWebアプリ(React)からAxiosでPOSTリクエストをして確認してみます。

web/src/App.tsx

import React, { useState, useEffect } from "react";
import "./App.css";
import axios from "axios";

const endpointUri = process.env.REACT_APP_REST_API_ENDPOINT_URI!;

const App: React.FC = () => {
  const [body, setBody] = useState<any>();
  useEffect(() => {
    axios.post(endpointUri).then((res) => {
      setBody(res.data);
    });
  }, []);

  return <div className="App">{body}</div>;
};

export default App;

まずバックエンドの修正を行わずPOSTリクエストをすると、CORSエラーとなりました。これは想定通りです。

REST APIのルートリソースにdefaultCorsPreflightOptionsを設定して、CORSを有効にします。

lib/aws-cdk-app-stack.ts

import * as cdk from "@aws-cdk/core";
import * as lambda from "@aws-cdk/aws-lambda";
import * as apigateway from "@aws-cdk/aws-apigateway";

export class AwsCdkAppStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const testFunction = new lambda.Function(this, "TestFunction", {
      code: lambda.Code.fromAsset("src/lambda/handlers"),
      functionName: "testFunction",
      handler: "index.handler",
      runtime: lambda.Runtime.NODEJS_14_X,
      memorySize: 128,
    });

    const restApi = new apigateway.RestApi(this, "RestApi", {
      restApiName: "test",
      deployOptions: {
        stageName: "v1",
      },
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: apigateway.Cors.ALL_METHODS,
        allowHeaders: apigateway.Cors.DEFAULT_HEADERS,
        statusCode: 200,
      },
    });

    restApi.root.addMethod(
      "GET",
      new apigateway.LambdaIntegration(testFunction)
    );

    restApi.root.addMethod(
      "POST",
      new apigateway.LambdaIntegration(testFunction)
    );
  }
}

cdk deployでスタックの変更をデプロイします。

ここでマネジメントコンソールからREST APIのリソースを見てみると、ルートにOPTIONSメソッドがきちんと追加されていることが確認できます。

WebアプリでPOSTリクエストを行うと、正常にLambdaからのレスポンスを取得できました。

参考

以上