APIGatewayの認可にCognitoのトークンを使用し、JavaScriptから実行する

# はじめに
ログイン機能のあるWEBサービスで利用されるAPIで、APIに認可を設定しなかった場合、APIのURLさえわかれば、どのユーザでもAPIが実行できてしまいます。
そのため、ログインユーザのみ許可されるAPIを作成する場合、適切な認可を設計する必要があります。

今回はAWS Cognitoでログインしているユーザのみ、APIGatewayで作成されたAPIが許可される設定をご紹介します。

※Cognitoは構築されている前提です。Cognitoの構築については以下の記事をご覧ください。
https://qiita.com/haruya_hamasaki/items/b4257ec4498185fa38a1
https://qiita.com/haruya_hamasaki/items/92d5972e46768a902104

# API Gateway の設定

構築に使用するSAMテンプレートの例です。
```json:template.yaml
MyMovieAPIGateway:
    Type: AWS::Serverless::Api
    Properties:
      AccessLogSetting:
        DestinationArn: !GetAtt ApigwLogDeliveryStream.Arn
        Format: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "caller":"$context.identity.caller", "user":"$context.identity.user","requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength" }\n'
      StageName: api
      ApiKeySourceType: HEADER
      EndpointConfiguration:
        Type: REGIONAL
      DefinitionBody:
        swagger: "2.0"
        info:
          description: "get userid"
          version: "1.0"
          title: "mymovie"
        basePath: "/api"
        schemes:
        - "https"
        paths:
          /movie:
            delete:
              consumes:
              - "application/json"
              produces:
              - "application/json"
              parameters:
              - name: "user_id"
                in: "query"
                required: false
                type: "string"
              - name: "title"
                in: "query"
                required: false
                type: "string"
              responses:
                "200":
                  description: "200 response"
                  schema:
                    $ref: "#/definitions/Empty"
                "400":
                  description: "400 response"
                "500":
                  description: "500 response"
              security:
              - MyMovie: []
              - api_key: []
              x-amazon-apigateway-integration:
                httpMethod: "POST"
                uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DelMovFunction.Arn}/invocations"
                responses:
                  default:
                    statusCode: "200"
                  ".*\"statusCode\": 500,.*":
                    statusCode: "500"
                  ".*\"statusCode\": 400,.*":
                    statusCode: "400"
                requestTemplates:
                  application/json: "{\n  \"user_id\": \"$input.params('user_id')\",\n  \"\
                    title\": \"$input.params('title')\"\n}"
                passthroughBehavior: "when_no_templates"
                contentHandling: "CONVERT_TO_TEXT"
                type: "aws"
        securityDefinitions:
          api_key:
            type: "apiKey"
            name: "x-api-key"
            in: "header"
          MyMovie:
            type: "apiKey"
            name: "Authorization"
            in: "header"
            x-amazon-apigateway-authtype: "cognito_user_pools"
            x-amazon-apigateway-authorizer:
              providerARNs:
              - !GetAtt MyMovieUserPool.Arn
              type: "cognito_user_pools"
```

上記だと余計な記載があり過ぎるので、認可の部分を抜粋します。

以下で、MyMovieと命名したオーソライザーを定義。
```json
          MyMovie:
            type: "apiKey"
            name: "Authorization"
            in: "header"
            x-amazon-apigateway-authtype: "cognito_user_pools"
            x-amazon-apigateway-authorizer:
              providerARNs:
              - !GetAtt MyMovieUserPool.Arn
              type: "cognito_user_pools"
```

以下で、オーソライザーを認可に設定しています。
```json
              security:
              - MyMovie: []
```

この設定で、APIのリクエストヘッダーにCognitoから付与されたトークンがないと、APIの実行が許可されない設定となりました。

# JavaScript　
今回はAPIをJavaScriptから実行します。
まず、ログインしているCognito UserPoolを指定し、情報を変数に格納します。
```javascript
// ユーザープールの設定
var user_pool_id = "{USER_POOL_ID}"
var client_id = "{CLIENT_ID}"
const poolData = {
  UserPoolId : user_pool_id,
  ClientId : client_id
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
const cognitoUser = userPool.getCurrentUser();
```

ログインユーザ情報から、トークンを抜き出します。
```javascript
cognitoUser.getSession(function(err, session) {
    id_token = session["idToken"]["jwtToken"]
});
```

ヘッダーにトークンを入れて、APIを実行します。
ログインユーザであれば、トークンが取得できているはずなので、APIが成功します。
逆にログインユーザ以外は失敗します。
```javascript
fetch(`https://{DOMAIN}/api/movie?user_id=${user_id}&title=${title}`,{
  //ヘッダーにトークンを入れる
  headers: {
    Authorization: id_token
  },
  //HTTPメソッド
  method: "delete"
})
.then(response => {
  if (!response.ok) {
    //失敗時の処理
  } else {
    //成功時の処理
  }
})  
.catch(error => {
  //失敗時の処理
});
```

# おまけ
本記事の機能は以下のサービスで利用されています。
気になった方は、お気軽にサービスも利用くださると大変嬉しいです。
GitHubには、今回紹介したコードが実際に使われている形であります。

#### 観た映画を登録、管理できるサービス　MyMovie
![映画一覧.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374911/8d8ac46a-8305-937f-5225-fb1a834309ad.png)

サービスURL : [mymovie.jp](https://mymovie.jp/)
GitHub(アプリ) : [github.com/hamasakiharuya/mymovie](https://github.com/hamasakiharuya/mymovie)
GitHub(CloudFormation/SAM) : [github.com/hamasakiharuya/sam-mymovie](https://github.com/hamasakiharuya/sam-mymovie)