Cognito UserPools Authorizerで認証をかけてあるLambda Function(Lambda Proxy Integration利用)において、認証ユーザに対応するCognito identityIdを取得

検索してもいまいち情報がなく、めちゃくちゃ試行錯誤した記憶があるのでメモしておきます。

# ■結論

認証Headerに含まれる`idToken`を元に、Lambda Function側で[GetId \- Amazon Cognito Federated Identities](https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetId.html)を叩いて`identityId`を取得する。

## 例: (Python3:boto3)

`event.headers.Authorization`にCognito認証に使ったidToken[^1]が含まれるので、それを利用。

[^1]: accessTokenではないので注意

```handler.py
import os
import re
import json
import boto3

COGNITO_USER_POOL_ARN = os.environ.get('COGNITO_USER_POOL_ARN')
COGNITO_IDENTITY_POOL_ID = os.environ.get('COGNITO_IDENTITY_POOL_ID')

def _get_identity_id_from_cognito_idToken(idToken):
    """
    cognitoIdentityIdを取得
    """
    pattern = 'arn:aws:cognito-idp:(?P<aws_region>.*):(?P<aws_account_id>[0-9]{12}):userpool/(?P<user_pool_id>.*)'
    m = re.match(pattern, COGNITO_USER_POOL_ARN)

    region = m.group('aws_region')
    account_id = m.group('aws_account_id')
    user_pool_id = m.group('user_pool_id')
    cognito_user_pool_keyname = f"cognito-idp.{region}.amazonaws.com/{user_pool_id}"

    # @see https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-identity.html#CognitoIdentity.Client.get_id
    response = boto3.client('cognito-identity', region).get_id(
        AccountId=account_id,
        IdentityPoolId=COGNITO_IDENTITY_POOL_ID,
        Logins={
            # Amazon Cognito user pool Example:
            #   "cognito-idp.us-east-1.amazonaws.com/us-east-1_123456789": "${idToken}"
            cognito_user_pool_keyname: idToken,
        }
    )
    return response['IdentityId']

def hello(event, context):
    idToken = event['headers']['Authorization']
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event,
        "cognito_id": _get_identity_id_from_cognito_idToken(idToken)
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response
```

以下にServerless Frameworkでの動作確認用サンプルを用意しました。長々と説明するよりわかりやすいかと思いますので、ご確認いただければ幸いです。
[terukizm/sls\-lambda\-proxy\-userpool\-auth\-sample](https://github.com/terukizm/sls-lambda-proxy-userpool-auth-sample)

IdentityProviderはCognito UserPoolだけに決め打ちしていますが、仮にFacebookとか他のやつも使う場合は、よしなに設定してやる必要があるんじゃないかと思います。(未確認)


# ■背景

以下の環境において、「Lambda Function側で」「identityIdを」取得する必要があった。

* Cognito + API Gateway + Lambda Function(Serverless Framework) + S3 の標準的なFaaS構成のREST API
  * 認証形式は`Cognito UserPools Authorizer` + `Lambda Proxy Integration`
     * slsでCognito認証がかかったREST APIを作る上で、一番簡単に設定できるため
         * API Gatewayのcontext mappingをするのはしんどそうだったのでlambda proxyで逃げたかった
     * `$ curl -sS -X GET -H "Authorization: ${idToken}"  "${ENDPOINT}/sample"` で動作確認済
  * REST APIの認証を通したユーザのみ、認証したidentity_idを含むパスの下のファイルを処理させたい  
     * [Amplify Storage](https://aws-amplify.github.io/docs/js/storage)の保存形式、 `s3://{bucket_name}/private/{identity_id}/`以下のファイル
       * Amplify SDKで作ったWebAppからファイルアップロードしたりすると上記のパスに格納される
 

以下のような方法はあったが、「Lambda Integration」もしくは「AWS_IAM」方式だったり、「eventに値が入ってこない」[^2]といった理由でidentityIdを取得することができなかった。

[^2]: claimsに含まれない。またrequestContext.identity以下に項目はあるが、値が入ってこない。

* 参考:
    * [LambdaにAPI Gatewayを経由してCognito Idを渡す \- Qiita](https://qiita.com/sekitaka_1214/items/dc1bd743d752c42ab355)
    * [Lambda function内からInvokeしたユーザのcognito IDを取得する方法 \- Qiita](https://qiita.com/bageljp@github/items/3354ccaedaa6a1e92979)

なお、Amplify SDKによってブラウザ側ではidentityIdを取得することができるが、それをバックエンド側にパラメータとして直接渡すのはためらわれた。このidentityIdが取得できない話は調べたところかなり昔から議論されてたっぽいが、おそらく正攻法ではいまのところIdentityIdを取得できないのではないかと思う。(2019/07時点)

* 参考:
    * [How to find the bidirectional map between Cognito identity ID and Cognito user information? · Issue \#54 · aws\-amplify/amplify\-js · GitHub](https://github.com/aws-amplify/amplify-js/issues/54)
    * [cognitoIdentityId · Issue \#685 · aws\-amplify/amplify\-js · GitHub](https://github.com/aws-amplify/amplify-js/issues/685)


というわけで、おとなしくLambda Function側でAWS APIを直接叩くことにした。[get_user](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp.html#CognitoIdentityProvider.Client.get_user)とか色々あったが、identity_idが取得でき、かつidTokenで処理できるものは[get_id](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-identity.html#CognitoIdentity.Client.get_id)しかなかったのでこれを利用した。


## (参考) Serverless Framework(sls)でのCognito UserPools Authorizer + Lambda Proxy Integration設定

* Serverless Framework
    * [lambda-proxy integration](https://serverless.com/framework/docs/providers/aws/events/apigateway/#lambda-proxy-integration)
    * [Cognito UserPool Authorizer](https://serverless.com/framework/docs/providers/aws/events/apigateway#http-endpoints-with-custom-authorizers)

```serverless.yml
functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get
          #integration: lambda-proxy (default)
          cors:
            origin: '*'
            headers:
              - Authorization
          authorizer:
            name: authorizer
            arn: ${env:COGNITO_USER_POOL_ARN}
```

ご参考になれば…