API GatewayのOPTIONSメソッド(CORS)でAPIキーを不要にする(AWS SAM)

API GatewayのCORS用のOPTIONSメソッドで、APIキーを不要にしてみました。
2024.02.29

API Gatewayを利用すればAPIを簡単に作成できます。 そして、APIキーの設定も簡単にできます。 このとき、CORS用のOPTIONSメソッドでAPIキー設定を次のようにしたかったので、試してみました。

  • OPTIONSメソッド: APIキーは不要でよい
  • 他のメソッド: APIキーが必須である

おすすめの方

  • API Gateway と Lambda の組み合わせでCORSに対応したい方
  • API GatewayのOPTIONSメソッド(CORS)でAPIキーを不要にしたい方

まずは、普通にAPIキーが必要なメソッドを作成する

sam init

sam init \
    --runtime python3.11 \
    --name api-gateway-no-required-api-key-in-options-method-sample \
    --app-template hello-world \
    --no-tracing \
    --no-application-insights \
    --structured-logging \
    --package-type Zip

SAMテンプレート

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: api-gateway-no-required-api-key-in-options-method-sample

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: v1
      OpenApiVersion: 3.0.1
      ApiKeySourceType: HEADER
      Auth:
        ApiKeyRequired: true
        UsagePlan:
          CreateUsagePlan: PER_API
          UsagePlanName: no-required-api-key-in-options-method-usage-plan
          Quota:
            Limit: 500
            Period: MONTH
          Throttle:
            BurstLimit: 100
            RateLimit: 50
      Cors:
        AllowMethods: "'DELETE,POST,GET,OPTIONS,PUT,PATCH'"
        AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
        AllowOrigin: "'https://www.example.com'"  # 実験のため、適当に設定しています。適切な値を設定してください。

HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.11
      Architectures:
      - x86_64
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
            RestApiId: !Ref MyApi

HelloWorldFunctionLogGroup:
      Type: AWS::Logs::LogGroup
      Properties:
        LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}

Lambdaコード

今回の実験ではブラウザからアクセスしないので不要ですが、「headers」も返しておきます。

app.py

import json

def lambda_handler(event, context):

<pre><code>return {
    &quot;statusCode&quot;: 200,
    &quot;headers&quot;: {
        &quot;Access-Control-Allow-Methods&quot;: &quot;DELETE,POST,GET,OPTIONS,PUT,PATCH&quot;,
        &quot;Access-Control-Allow-Headers&quot;: &quot;Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token&quot;,
        &quot;Access-Control-Allow-Origin&quot;: &quot;https://www.example.com",
    },
    &quot;body&quot;: json.dumps(
        {
            &quot;message&quot;: &quot;hello world&quot;,
        }
    ),
}
</code></pre>

デプロイ

sam deploy \
    --guided \
    --region ap-northeast-1 \
    --stack-name api-gateway-no-required-api-key-in-options-method-sample-stack

OPTIONSメソッドでAPIキーが必須になっていることを確認する

上記の内容をデプロイしたとき、API Gatewayの動作は、APIキーが無いと403が返ってきます。

OPTIONSメソッド(APIキーなし)は、403が返ってくる

$ curl -X OPTIONS -I \
    https://xxx.execute-api.ap-northeast-1.amazonaws.com/v1/hello

HTTP/2 403

OPTIONSメソッド(APIキーあり)は、200が返ってくる

$ curl -X OPTIONS -I \
    -H "x-api-key: valid-api-key" \
    https://xxx.execute-api.ap-northeast-1.amazonaws.com/v1/hello

HTTP/2 200

GETメソッド(APIキーなし)は、403が返ってくる

$ curl -X GET -I \
    https://xxx.execute-api.ap-northeast-1.amazonaws.com/v1/hello

HTTP/2 403

GETメソッド(APIキーあり)は、200が返ってくる

$ curl -X GET -I \
    -H "x-api-key: valid-api-key" \
    https://xxx.execute-api.ap-northeast-1.amazonaws.com/v1/hello

HTTP/2 200

OPTIONSメソッドは、APIキーを不要に設定する

SAMテンプレート

「AddApiKeyRequiredToCorsPreflight」を追加しました。未指定の場合は、デフォルトでtrueが設定されるのです。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: api-gateway-no-required-api-key-in-options-method-sample

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: v1
      OpenApiVersion: 3.0.1
      ApiKeySourceType: HEADER
      Auth:
        ApiKeyRequired: true
        AddApiKeyRequiredToCorsPreflight: false
        UsagePlan:
          CreateUsagePlan: PER_API
          UsagePlanName: no-required-api-key-in-options-method-usage-plan
          Quota:
            Limit: 500
            Period: MONTH
          Throttle:
            BurstLimit: 100
            RateLimit: 50
      Cors:
        AllowMethods: "'DELETE,POST,GET,OPTIONS,PUT,PATCH'"
        AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
        AllowOrigin: "'https://www.example.com'"  # 実験のため、適当に設定しています。適切な値を設定してください。

HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.11
      Architectures:
      - x86_64
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
            RestApiId: !Ref MyApi

HelloWorldFunctionLogGroup:
      Type: AWS::Logs::LogGroup
      Properties:
        LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}

デプロイ

sam deploy

OPTIONSメソッド(APIキーなし)は、200が返ってくる

目論見通り、403ではなく、200が返ってきました。

$ curl -X OPTIONS -I \
    https://xxx.execute-api.ap-northeast-1.amazonaws.com/v1/hello

HTTP/2 200

OPTIONSメソッド(APIキーあり)は、200が返ってくる

$ curl -X OPTIONS -I \
    -H "x-api-key: valid-api-key" \
    https://xxx.execute-api.ap-northeast-1.amazonaws.com/v1/hello

HTTP/2 200

GETメソッド(APIキーなし)は、403が返ってくる

$ curl -X GET -I \
    https://xxx.execute-api.ap-northeast-1.amazonaws.com/v1/hello

HTTP/2 403

GETメソッド(APIキーあり)は、200が返ってくる

$ curl -X GET -I \
    -H "x-api-key: valid-api-key" \
    https://xxx.execute-api.ap-northeast-1.amazonaws.com/v1/hello

HTTP/2 200

参考