ChatGPTにLambda Authorizerの開発手順を聞いてみたらスゴすぎた

## 質問文1

「Samでnode.jsで複数のlambda関数を作成して、さらにトークン認証のLambda Authorizerを作成して全てのlambda関数にトークン認証のLambda Authorizerを適用する方法を丁寧に教えてください。」


### 回答
AWS SAMを使用してNode.jsで複数のLambda関数を作成し、それらのLambda関数にトークン認証のLambda Authorizerを適用する方法は以下の手順に従って行います。

1.プロジェクトの作成
最初に、AWS SAM CLIを使用して新しいプロジェクトを作成します。
```terminal:

sam init --runtime nodejs14.x --name my-serverless-app

```
これにより、Node.js 14.xランタイムを使用する新しいプロジェクトが作成されます。

2.Lambda関数の作成
次に、Lambda関数を追加します。 template.yml ファイルに以下のようなコードを追加します。
```yml:template.yml

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.handler
      Runtime: nodejs14.x
      Events:
        HelloWorldApi:
          Type: Api
          Properties:
            Path: /hello
            Method: get

```
これにより、hello-world ディレクトリに app.js ファイルを作成し、以下のようなコードを追加します。






```js:hello-world/app.js

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

  return response;
};
```
これにより、 /hello パスで GET リクエストを受け取ると、Hello World! メッセージが含まれるJSONレスポンスが返されるLambda関数が作成されます。





3.Lambda Authorizerの作成
次に、Lambda Authorizerを追加します。 template.yml ファイルに以下のようなコードを追加します。
```yml:template.yml

Resources:
  TokenAuthorizer:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: token-authorizer/
      Handler: app.handler
      Runtime: nodejs14.x
      Events:
        TokenAuthorizerEvent:
          Type: TokenAuthorizer
          Properties:
            RestApiId: !Ref HelloWorldApi
            AuthorizerResultTtlInSeconds: 300



```
これにより、token-authorizer ディレクトリに app.js ファイルを作成し、以下のようなコードを追加します。

```js:token-authorizer/app.js

module.exports.handler = async (event) => {
  const token = event.authorizationToken;
  const methodArn = event.methodArn;

  // 認証処理を行い、allow/denyの結果を返す処理を追加

  return {
    principalId: 'user',
    policyDocument: {
      Version: '2012-10-17',
      Statement: [
        {
          Action: 'execute-api:Invoke',
          Effect: effect,
          Resource: methodArn
        }
      ]
    }
  };
};



///

exports.handler = async (event) => {
  // 受け取ったBearerトークンを取得
  const token = event.authorizationToken.split(' ')[1];

  try {
    // トークンを検証し、有効であることを確認する
    const decoded = jwt.verify(token, process.env.SECRET_KEY);
    // ユーザーに対するアクセス許可を許可する場合はポリシーを返す
    return generatePolicy(decoded.sub, 'Allow', event.methodArn);
  } catch (err) {
    // トークンが無効である場合はアクセスを拒否するポリシーを返す
    return generatePolicy('user', 'Deny', event.methodArn);
  }
};

// IAMポリシーを生成するヘルパー関数
function generatePolicy(principalId, effect, resource) {
  const authResponse = {};
  authResponse.principalId = principalId;
  if (effect && resource) {
    const policyDocument = {};
    policyDocument.Version = '2012-10-17';
    policyDocument.Statement = [];
    const statementOne = {};
    statementOne.Action = 'execute-api:Invoke';
    statementOne.Effect = effect;
    statementOne.Resource = resource;
    policyDocument.Statement[0] = statementOne;
    authResponse.policyDocument = policyDocument;
  }
  return authResponse;
}



////

exports.handler = function(event, context, callback) {
  // Extract the token from the event
  var token = event.authorizationToken;
  
  // Verify the token
  if (token === 'Bearer my-token') {
    // Generate a policy that allows the user to invoke the API Gateway
    var policy = generatePolicy('user', 'Allow', event.methodArn);
    callback(null, policy);
  } else {
    // Deny access
    callback('Unauthorized');
  }
};

function generatePolicy(principalId, effect, resource) {
  var authResponse = {};
  
  authResponse.principalId = principalId;
  if (effect && resource) {
    var policyDocument = {};
    policyDocument.Version = '2012-10-17';
    policyDocument.Statement = [];
    var statementOne = {};
    statementOne.Action = 'execute-api:Invoke';
    statementOne.Effect = effect;
    statementOne.Resource = resource;
    policyDocument.Statement[0] = statementOne;
    authResponse.policyDocument = policyDocument;
  }
  return authResponse;
}

```

これにより、Lambda Authorizer関数が作成され、API Gatewayの /hello パスにトークン認証が適用されます。


4.Lambda関数にLambda AuthorizerをLambda関数に適用します。

template.ymlファイルでLambda関数の定義を以下のように変更します

```yml:template.yml
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.handler
      Runtime: nodejs14.x
      Events:
        HelloWorldApi:
          Type: Api
          Properties:
            Path: /hello
            Method: get
            Auth:# 追加
            　　Authorizers:
            　　　　MyAuthorizer:　FunctionArn: !GetAtt MyAuthorizerFunction.Arn
            　　　　Identity:
            Headers:
            　- Authorization
             ResultTtlInSeconds: 300# ここまで追加


```
これにより、/helloパスにGETリクエストを送信する前に、API GatewayがLambda Authorizerを呼び出し、トークン認証を行います。トークン認証が成功した場合、Lambda関数が実行されます。

以上で、AWS SAMを使用してNode.jsで複数のLambda関数を作成し、トークン認証のLambda Authorizerを適用する手順が完了しました。必要に応じて、Lambda Authorizer関数に適切な認証処理を追加することができます。

## 質問文2
「これをローカルで動作確認する方法を丁寧に教えて」


### 回答
AWS SAM CLIを使用して、先程作成したLambda関数とLambda Authorizerをローカルで実行して動作確認を行うことができます。以下はその手順です。

1.プロジェクトのビルド
まず、AWS SAM CLIを使用してプロジェクトをビルドします。

```terminal:

sam build

```

これにより、依存関係をインストールし、Lambda関数とLambda Authorizerのコードをビルドします。

2.ローカルでのLambda関数の実行
次に、ローカルでLambda関数を実行して動作を確認します。

```bash:

sam local start-api

```

これにより、API Gatewayがローカルで起動し、http://127.0.0.1:3000/helloでLambda関数にリクエストを送信できます。

3.ローカルでのトークン認証のテスト
ローカルでトークン認証をテストするために、以下のようなコマンドを使用してLambda Authorizer関数を実行します。

```bash:

sam local invoke TokenAuthorizer --event events/token-authorizer.json

```
これにより、トークン認証のテストが行われ、ポリシードキュメントが返されます。

以上で、AWS SAM CLIを使用して、Node.jsで作成したLambda関数とLambda Authorizerをローカルで実行して動作確認を行うことができます。必要に応じて、eventsディレクトリに新しいイベントファイルを作成して、より複雑なシナリオでの動作確認を行うことができます。


以下は、token-authorizer.jsonファイルの例です。

```json:events/token-authorizer.json
{
  "type": "TOKEN",
  "authorizationToken": "Bearer my-token",
  "methodArn": "arn:aws:execute-api:us-west-2:123456789012:my-api-id/dev/GET/hello"
}


```
この例では、Bearerトークンmy-tokenを使用してLambda Authorizerを呼び出し、API Gatewayの/helloエンドポイントにGETリクエストを送信することを想定しています。methodArnプロパティには、Lambda Authorizerを呼び出すAPI Gatewayのリソースアーノテーションが指定されています。必要に応じて、authorizationTokenとmethodArnプロパティを変更して、テストするイベントをカスタマイズできます。

[通常]
event.methodArnは、Lambda Authorizerが呼び出されるAPI GatewayリソースのARN（Amazon Resource Name）です。ARNは、AWSリソースを一意に識別するための文字列です。

API Gatewayの場合、event.methodArnには、API Gatewayのリソースを指定するARNが含まれます。具体的には、arn:aws:execute-api:{region}:{account_id}:{api_id}/{stage}/{http_method}/{resource_path}のような形式で表されます。ここで、{region}はAWSリージョン名、{account_id}はAWSアカウントID、{api_id}はAPI GatewayのREST APIのID、{stage}はAPI Gatewayステージ名、{http_method}はHTTPメソッド、{resource_path}はAPI Gatewayリソースパスを表します。

Lambda Authorizerは、このARNを使用して、API Gatewayの特定のリソースに対するアクセス権限を決定するために使用されます。つまり、ARNを解析して、呼び出し元がアクセスしようとしているAPI Gatewayリソースを特定し、ユーザーに対するアクセス許可を判断するロジックを実行します。


[ローカル]
ローカルでLambda Authorizerをテストする場合、event.methodArnには実際のAPI GatewayのARNが入ることを想定します。ただし、ローカル環境ではAPI Gatewayが実行されていないため、テストイベントでmethodArnの値をモック化する必要があります。

テストイベントでmethodArnの値をモック化する場合、実際のAPI GatewayのARNに似た値を設定することが推奨されています。例えば、以下のような形式で指定することができます。
```json:

{
  "methodArn": "arn:aws:execute-api:us-east-1:123456789012:xxxxxx/dev/GET/users"
}

```
ここで、xxxxxxはAPI GatewayのREST APIのIDに置き換え、GETはHTTPメソッド、/usersはAPI Gatewayリソースパスを表します。これにより、Lambda Authorizerが正常に動作するかどうかを確認することができます。



### 質問の仕方を変えてみる
「samでnode.jsでLambda Authorizerとlambda関数をそれぞれ作成して認証適用する場合の書き方の詳細を教えて」

## 回答
```yml
Resources:
  LambdaAuthorizerFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs14.x
      CodeUri: .
      Description: Lambda Authorizer function
      MemorySize: 128
      Timeout: 30
      Events:
        Authorizer:
          Type: Api
          Properties:
            RestApiId:
              Ref: MyApi
            Path: /auth
            Method: get
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs14.x
      CodeUri: .
      Description: Lambda function
      MemorySize: 128
      Timeout: 30
      Events:
        GetResource:
          Type: Api
          Properties:
            RestApiId:
              Ref: MyApi
            Path: /resource
            Method: get
            AuthorizationType: CUSTOM
            AuthorizerId:
              Ref: LambdaAuthorizerFunction
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
      DefinitionBody:
        swagger: "2.0"
        info:
          version: "1.0"
          title: "My API"
        paths:
          /resource:
            get:
              x-amazon-apigateway-integration:
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations
                httpMethod: POST
                responses: {}
          /auth:
            get:
              x-amazon-apigateway-integration:
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaAuthorizerFunction.Arn}/invocations
                httpMethod: POST
                responses: {}

```
### 質問
​
ローカルでLambdaFunctionの認証動作確認を行うには
```bash
sam local start-lambda --template template.yaml

sam local invoke <authorizer_function_logical_id> -e authorizer-event.json

sam local start-api

curl -H "Authorization: Bearer <token>" http://localhost:3000/hello

///////////////

sam local start-api

curl http://localhost:3000/auth

curl -H "Authorization: <Token>" http://localhost:3000/resource
```
```
aws lambda publish-layer-version --layer-name my-layer --zip-file fileb://my-layer.zip --compatible-runtimes nodejs14.x
```
```yml
Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      Handler: index.handler
      Runtime: nodejs14.x
      Layers:
        - !Ref MyLayer

  MyLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: my-layer
      ContentUri: ./my-layer.zip
      CompatibleRuntimes:
        - nodejs14.x```



```