7
7

More than 5 years have passed since last update.

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

Posted at

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

■結論

認証Headerに含まれるidTokenを元に、Lambda Function側でGetId - Amazon Cognito Federated Identitiesを叩いてidentityIdを取得する。

例: (Python3:boto3)

event.headers.AuthorizationにCognito認証に使ったidToken1が含まれるので、それを利用。

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

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の保存形式、 s3://{bucket_name}/private/{identity_id}/以下のファイル
      • Amplify SDKで作ったWebAppからファイルアップロードしたりすると上記のパスに格納される

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

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

というわけで、おとなしくLambda Function側でAWS APIを直接叩くことにした。get_userとか色々あったが、identity_idが取得でき、かつidTokenで処理できるものはget_idしかなかったのでこれを利用した。

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

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}

ご参考になれば…


  1. accessTokenではないので注意 

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

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7