Help us understand the problem. What is going on with this article?

API GatewayのIAM認証をCOGNITOユーザプールで試してみた

こんにちは。
インフラ・エンジニアです。

AWS API Gateway のIAM認証を試してみました。
IAM認証で使用するロールは、COGNITOユーザープールに作成したグループに設定し、
このロールでAPIが実行できるか試してみました。

検証環境の構築は、CloudFormation でやってみたので、手順を紹介します。
ここでは、ソースなどの細かい説明は割愛していますので、
参考にしたサイトやAWSマニュアルの説明など、他のよくできたページを見てください。

■前提
・作業はMACで実施
・awscliが作業用のMACにインストール済み。 コマンド実行時は profileを指定。

$ python --version
Python 2.7.15
$ aws --version
aws-cli/1.16.17 Python/2.7.15 Darwin/17.7.0 botocore/1.12.7

■参考にしたサイト
https://qiita.com/ykarakita/items/f58f861165db5b81aea0
https://aws.amazon.com/jp/blogs/mobile/building-fine-grained-authorization-using-amazon-cognito-user-pools-groups/
https://forums.aws.amazon.com/thread.jspa?threadID=255584

1. API Gateway の構築

API Gatewayを構築します。
IAM認証のGETメソッドをMOCで1つ作成して、デプロイします。
GETメソッドのレスポンスはJSONです。
CloudFormationのソースは以下の通り。

SandboxApiGateay.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: API Gateway Sandbox

Resources:

  # Rest API
  RestAPI:
    Type: 'AWS::ApiGateway::RestApi'
    Properties:
      Description: sandbox
      Name: !Ref 'AWS::StackName'
      EndpointConfiguration:
        Types:
          - REGIONAL

  # GET on the /. MOCK json response
  RestAPIMockGET:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      ApiKeyRequired: false
      AuthorizationType: AWS_IAM
      HttpMethod: GET
      Integration:
        IntegrationHttpMethod: GET
        PassthroughBehavior: WHEN_NO_MATCH
        Type: MOCK
        RequestTemplates:
          application/json: |
              {
                "statusCode": 200
              }
        IntegrationResponses:
          - StatusCode: 200
            ResponseParameters:
              method.response.header.Content-Type: "'application/json'"
            ResponseTemplates:
              application/json: |
                  {
                    "message": "hello!"
                  }
      MethodResponses:
        - StatusCode: 200
          ResponseModels:
            text/html: Empty
          ResponseParameters:
            method.response.header.Content-Type: true
      ResourceId: !GetAtt RestAPI.RootResourceId
      RestApiId: !Ref RestAPI
    DependsOn:
      - RestAPI

  # Deploy
  ApiDeployment:
    Type: 'AWS::ApiGateway::Deployment'
    Properties:
      RestApiId: !Ref RestAPI
      Description: sandbox deployment
    DependsOn:
      - RestAPIMockGET

  # create stage01
  ApiStage01:
    Type: 'AWS::ApiGateway::Stage'
    Properties:
      RestApiId: !Ref RestAPI
      DeploymentId: !Ref ApiDeployment
      StageName: stage1
      MethodSettings:
        - ResourcePath: /*
          HttpMethod: '*'

aws-cli で、このソースコードをチェックします。

$ aws cloudformation validate-template --profile sandbox --template-body file://`pwd`/SandboxApiGateway.yaml
{
    "Description": "API Gateway Sandbox", 
    "Parameters": []
}

エラーが表示されなければ、aws-cli でスタックを作成します。
スタック名がAPIGatewayのAPI名になります。

$ aws cloudformation create-stack --profile sandbox --stack-name SandboxApi --template-body file://`pwd`/SandboxApiGateway.yaml

AWSコンソールでCloudFormationの実行結果を確認します。 [状況]が CREATE_COMPLETE であればOKです。

20181122_01.png

AWSコンソールでAPI Gatewayのエンドポイント確認します。

20181122_02.png

curlコマンドでAPIを実行してみます。
認証してないのでエラーになります。
以下のようにエラーメッセージが表示されたらOKです。

$ curl -XGET https://2037kuo2d5.execute-api.ap-northeast-1.amazonaws.com/stage1/
{"message":"Missing Authentication Token"}

2. COGNITOの構築

2.1. 動的マッピング用の Lambda関数を登録

COGNITOのIDプールをCloudFormationで構築しますが、
認証プロバイダの設定で、CloudFormationのカスタムリソースを使用します。
ここでは、カスタムリソースで使用するLambda関数を登録します。

CloudFormationのソースコードは以下の通り。

SandboxCognitoMapping.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Custom resource for cognito role mapping.
Resources:
  DynamicMapTransformLambda:
    Type: AWS::Lambda::Function
    Properties:
      Description: Transform to generate maps with computed keys
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: nodejs6.10
      Code:
        ZipFile: !Sub |
          const { send, SUCCESS, ERROR } = require('cfn-response');
          exports.handler = (event, context, callback) => {
            console.log(JSON.stringify(event, null, 2));
            const { RequestType, ResourceProperties: props = { } } = event;
            const { Entries: entries = [ ], AttributeName: attName } = props;

            switch(RequestType) {
              case 'Create':
              case 'Update':
                const result = { };
                for (let i = 0; i < entries.length; i++) {
                  const { Key, Value } = entries[i]
                  if (Key) {
                    result[Key] = Value
                  }
                }
                send(event, context, SUCCESS, { [attName]: result });
                break;
              case 'Delete':
                send(event, context, SUCCESS);
                break;
            }
          };
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action: "sts:AssumeRole"
          Principal:
            Service: lambda.amazonaws.com
      Path: "/"
      Policies:
      - PolicyName: root
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action: "logs:*"
            Resource: "arn:aws:logs:*:*:*"
Outputs:
  TransformLambda:
    Description: The CloudFormation Custom ServiceToken Function Arn
    Value: !GetAtt DynamicMapTransformLambda.Arn
    Export:
      Name: TransformLambda

aws-cli でスタックを作成します。

$ aws cloudformation create-stack --profile sandbox --stack-name SandboxCognitoMap --template-body file://`pwd`/SandboxCognitoMapping.yaml --capabilities CAPABILITY_IAM

2.2. COGNITOのIDプール、ユーザプール構築とユーザ登録

ここでは、COGNITOのIDプール、ユーザプールを作成し、ユーザ/グループを登録します。
また、APIGatewayのAPI実行を許可するロールを作成して、グループに紐つけます。

ユーザの作成時に使用するメールアドレスは、メール受信可能なものを使用します。
あとで、仮パスワードをメールするので、仮パスワードを変更してユーザをアクティブにします。
CloudFormationのソースコードは以下の通り。

SandboxCognito.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Cognito Identity Pool and User Pool."
Resources:

  # Cognito User Pool
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: !Sub "${AWS::StackName}Users"
      AliasAttributes:
        - email
      AutoVerifiedAttributes:
        - email
      Schema:
        - AttributeDataType: "String"
          Name: email
          Required: True
  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      ClientName: !Sub "${AWS::StackName}Users-client"
      GenerateSecret: false
      ExplicitAuthFlows:
        - ADMIN_NO_SRP_AUTH
      RefreshTokenValidity: 7
      UserPoolId:
        Ref: UserPool

  # Cognito Identity Pool
  IdentityPool:
    Type: AWS::Cognito::IdentityPool
    Properties:
      AllowUnauthenticatedIdentities: true
      IdentityPoolName: !Sub "${AWS::StackName}Id"
      CognitoIdentityProviders:
      - ClientId: !Ref UserPoolClient
        ProviderName: !GetAtt UserPool.ProviderName
  UnauthenticatedRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Action: "sts:AssumeRoleWithWebIdentity"
          Principal:
            Federated: cognito-identity.amazonaws.com
          Condition:
            StringEquals:
              "cognito-identity.amazonaws.com:aud": !Ref IdentityPool
            ForAnyValue:StringLike:
              "cognito-identity.amazonaws.com:amr": unauthenticated
      Path: "/"
      Policies:
      - PolicyName: cognito
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - mobileanalytics:PutEvents
                - cognito-sync:*
              Resource:
                - "*"
      - PolicyName: api
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Deny
              Action:
                - execute-api:Invoke
              Resource:
                - "*"
  AuthenticatedRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Action: "sts:AssumeRoleWithWebIdentity"
          Principal:
            Federated: cognito-identity.amazonaws.com
          Condition:
            StringEquals:
              "cognito-identity.amazonaws.com:aud": !Ref IdentityPool
            ForAnyValue:StringLike:
              "cognito-identity.amazonaws.com:amr": authenticated
      Path: "/"
      Policies:
        - PolicyName: cognito
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - mobileanalytics:PutEvents
                  - cognito-sync:*
                  - cognito-identity:*
                Resource:
                  - "*"
  IdentityPoolRoleAttachmentMapping:
    Type: Custom::DynamicMapTransform
    Properties:
      ServiceToken: !ImportValue TransformLambda
      AttributeName: RoleMappings
      Entries:
        - Key: !Sub ${UserPool.ProviderName}:${UserPoolClient}
          Value:
            Type: Token
            AmbiguousRoleResolution: Deny
  RoleAttachment:
    Type: AWS::Cognito::IdentityPoolRoleAttachment
    Properties:
      IdentityPoolId: !Ref IdentityPool
      Roles:
        unauthenticated: !GetAtt UnauthenticatedRole.Arn
        authenticated: !GetAtt AuthenticatedRole.Arn
      RoleMappings: !GetAtt IdentityPoolRoleAttachmentMapping.RoleMappings

  # Cognito User / Group
  UserPoolGroup:
    Type: AWS::Cognito::UserPoolGroup
    Properties:
      GroupName: exgroup
      RoleArn: !GetAtt GroupRole.Arn
      UserPoolId: !Ref UserPool
      Precedence: 0
  UserPoolUser:
    Type: AWS::Cognito::UserPoolUser
    Properties:
      DesiredDeliveryMediums: 
        - EMAIL
      UserAttributes: 
        - Name: email
          Value: loco-shiroma@example.com
        - Name: email_verified
          Value: true
      MessageAction: SUPPRESS
      Username: exuser
      UserPoolId: !Ref UserPool
  UserGroupAttach:
    Type: AWS::Cognito::UserPoolUserToGroupAttachment
    Properties:
      GroupName: exgroup
      Username: exuser
      UserPoolId: !Ref UserPool
    DependsOn:
      - UserPoolGroup
      - UserPoolUser
  GroupRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Action: "sts:AssumeRoleWithWebIdentity"
          Principal:
            Federated: cognito-identity.amazonaws.com
          Condition:
            StringEquals:
              "cognito-identity.amazonaws.com:aud": !Ref IdentityPool
      Path: "/"
      Policies:
        - PolicyName: api
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
            - Effect: Allow
              Action:
                - execute-api:Invoke
                - execute-api:InvalidateCache
              Resource:
                - "*"

aws-cliでスタックを作成します。

$ aws cloudformation create-stack --profile sandbox --stack-name SandboxCognito --template-body file://`pwd`/SandboxCognito.yaml --capabilities CAPABILITY_IAM

AWSコンソールを開いて、API実行に必要な値を確認してメモしておきます。
まず、IDプールのIDを確認します。

20181127-02.png

ユーザプールのIDを確認します。

20181127-03.png

アプリクライアントのIDを確認します。

20181127-04.png

2.3 仮パスワードの変更

上記2.2でユーザが登録されますが、下図のようにステータスが、FORCE_CHANGE_PASSWORDと表示されます。

20181127-01.png

このままでは、ユーザを使用できないので、パスワードを変更します。
まず、ユーザプールのIDを指定して、 以下のように aws-cli を実行して仮パスワードをメールします。

$ aws cognito-idp admin-create-user --user-pool-id ${uid} --username exuser --message-action RESEND --profile sandbox
{
    "User": {
        "Username": "exuser", 
        "Enabled": true, 
        "UserStatus": "FORCE_CHANGE_PASSWORD", 
        "UserCreateDate": 1543298193.167, 
        "UserLastModifiedDate": 1543300350.554, 
        "Attributes": [
            {
                "Name": "sub", 
                "Value": "e105d2dc-4f76-4ad1-99e0-5d55be36986a"
            }, 
            {
                "Name": "email_verified", 
                "Value": "true"
            }, 
            {
                "Name": "email", 
                "Value": "loco-shiroma@example.com"
            }
        ]
    }
}

下図のようにメールが届きます。
ピリオドは、パスワードに含みません。

スクリーンショット 2018-11-27 15.34.18.png

メールでもらったパスワードでログインします。

$ aws cognito-idp admin-initiate-auth --user-pool-id ${uid} --client-id ${cid} --auth-flow ADMIN_NO_SRP_AUTH --auth-parameters "USERNAME=exuser,PASSWORD=${pwd}" --profile sandbox
{
    "ChallengeName": "NEW_PASSWORD_REQUIRED", 
    "ChallengeParameters": {
        "USER_ID_FOR_SRP": "exuser", 
        "requiredAttributes": "[]", 
        "userAttributes": "{\"email_verified\":\"true\",\"email\":\"loco-shiroma@example.com\"}"
    }, 
    "Session": "jOaPpSiBKzxV0UU8qtkgGCscprHzrerFFefuugP7km_jvYrZpgidrF5H1GxyHfhKj3pROwGxFlt2fzlQjhfvomKyUW-Sy-Q1LvY8gyW2UkDRN1CaPap_Kkh7wGVa_PeXNlp1uAhDqc0Jro-Ga3c5Hucruvkp5oZKt30c3iIyULwTNdBL619HEUWQhFACjkxn1NZSLQUI_dhszdB_M4bCG-7BaRDBcD1ZQazmONtWuMDjReK1vg_HVVWiXMRs-0ZKva_hdKt7NwJyCBysUAjS3LxSHNOFywfhJ4XWMLuU7oq0w7_7Z1U1_sEhDLJzwFLKe-PIG9ihFJkkf-UVvFUQG2EElewFY9xmuRNySjQQ-gHgB2J80gAK8n6EyCluT8TTLfPfeC-Vpe1XGTOMYbv9GLvl8CBlrVvIBfhKuZp-5fA4AQZcnns75oi69Qgwxw6rRdq2T-cweYaXHMi3Z7c8flf6DAurCRKEdhed2YQ6BI0m4GDr_qFa0Mdb9V5Zi9tsiVg9y7qX3N05GIV2c_OIby_SqETHWRRqXaNiJGTRgxCYdd-1jkt5VUz8DVqgtQEJTH04tFH6d-tagtScBaP5ERB1Bkfs0GXobRSHP7PhaFG7EgShqpZ1Eq_jMeJywqIIlH8HLcaBpcCITKMU-_QUY-fghrvQ4QLVO17-MlquTvfJL58XBc-KUaZju1RJPYtVNgxtbJhPUEz7CVJVnda19_-FUibf93fLV6klLoZr-AozPyti40UDCcPOX-ArZi6f2HRBjGZ3mDsbkQm495FuHPzbEsIMMlDdivJKy-MBxrc6BfvxeAcXaE6_bPDlcclvet5zxpXQ2n5FB2FrWNEQKQ"
}

上記コマンドで表示された Sessionの値を使用してパスワードを変更します。

$ aws cognito-idp admin-respond-to-auth-challenge --user-pool-id ${uid} --client-id ${cid} --challenge-name NEW_PASSWORD_REQUIRED --challenge-responses NEW_PASSWORD='Passw0rd!',USERNAME=exuser --session "jOaPpSiBKzxV0UU8qtkgGCscprHzrerFFefuugP7km_jvYrZpgidrF5H1GxyHfhKj3pROwGxFlt2fzlQjhfvomKyUW-Sy-Q1LvY8gyW2UkDRN1CaPap_Kkh7wGVa_PeXNlp1uAhDqc0Jro-Ga3c5Hucruvkp5oZKt30c3iIyULwTNdBL619HEUWQhFACjkxn1NZSLQUI_dhszdB_M4bCG-7BaRDBcD1ZQazmONtWuMDjReK1vg_HVVWiXMRs-0ZKva_hdKt7NwJyCBysUAjS3LxSHNOFywfhJ4XWMLuU7oq0w7_7Z1U1_sEhDLJzwFLKe-PIG9ihFJkkf-UVvFUQG2EElewFY9xmuRNySjQQ-gHgB2J80gAK8n6EyCluT8TTLfPfeC-Vpe1XGTOMYbv9GLvl8CBlrVvIBfhKuZp-5fA4AQZcnns75oi69Qgwxw6rRdq2T-cweYaXHMi3Z7c8flf6DAurCRKEdhed2YQ6BI0m4GDr_qFa0Mdb9V5Zi9tsiVg9y7qX3N05GIV2c_OIby_SqETHWRRqXaNiJGTRgxCYdd-1jkt5VUz8DVqgtQEJTH04tFH6d-tagtScBaP5ERB1Bkfs0GXobRSHP7PhaFG7EgShqpZ1Eq_jMeJywqIIlH8HLcaBpcCITKMU-_QUY-fghrvQ4QLVO17-MlquTvfJL58XBc-KUaZju1RJPYtVNgxtbJhPUEz7CVJVnda19_-FUibf93fLV6klLoZr-AozPyti40UDCcPOX-ArZi6f2HRBjGZ3mDsbkQm495FuHPzbEsIMMlDdivJKy-MBxrc6BfvxeAcXaE6_bPDlcclvet5zxpXQ2n5FB2FrWNEQKQ" --profile sandbox
{
    "AuthenticationResult": {
        "ExpiresIn": 3600, 
        "IdToken": "eyJraWQiOiJnajkwencxK2VLZE53a09xNVJHNU5Ma3hic1pkNXBvSG1Db3F3bTZBcGhvPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJlMTA1ZDJkYy00Zjc2LTRhZDEtOTllMC01ZDU1YmUzNjk4NmEiLCJjb2duaXRvOmdyb3VwcyI6WyJleGdyb3VwIl0sImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJjb2duaXRvOnByZWZlcnJlZF9yb2xlIjoiYXJuOmF3czppYW06OjM1MzYzNjUxNzQxNzpyb2xlXC9TYW5kYm94Q29nbml0by1Hcm91cFJvbGUtMUlEMDBIQkZURVgwQSIsImlzcyI6Imh0dHBzOlwvXC9jb2duaXRvLWlkcC5hcC1ub3J0aGVhc3QtMS5hbWF6b25hd3MuY29tXC9hcC1ub3J0aGVhc3QtMV9BdFJzTUZUcTAiLCJjb2duaXRvOnVzZXJuYW1lIjoiZXh1c2VyIiwiY29nbml0bzpyb2xlcyI6WyJhcm46YXdzOmlhbTo6MzUzNjM2NTE3NDE3OnJvbGVcL1NhbmRib3hDb2duaXRvLUdyb3VwUm9sZS0xSUQwMEhCRlRFWDBBIl0sImF1ZCI6IjNpNjduYTU2Z3ZlaWk3cWtmMGxpa3NwdGw4IiwiZXZlbnRfaWQiOiJmZDI1ZmZmNi1mMjBlLTExZTgtODFmMC1iZmY1MjZiNTcxZmQiLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTU0MzMwMDY4MiwiZXhwIjoxNTQzMzA0MjgyLCJpYXQiOjE1NDMzMDA2ODIsImVtYWlsIjoicy5zaGlyb21hQGxvY28tcGFydG5lcnMuY29tIn0.MPiuA6x-MnnzB55JWrZT8y1zuwWVNBxCNgLAz8NtNL6Lt_EbJVeNzjS3WgaPQhihXbeOxCSoQeoV_RGilGJblW7kfHeIyMOY3pA1Bl7uykyTI2gt8iiOvwufCjYGhU5prNAZYUbsmzkvoZDfs1QGjQi2CciRqSkxVzDxj_g3d6KFqYceUuPbjyWUXX-VswH-Kj12pQo6ns6yMjI-PC3-LjuFbMSjR9vJPOl8OzVvDSnIKxfSCNGk1K4gF4P3me8bXCarCRYol6oTRBjxbFbPip9kh7nue2lnxQcOV-MWkkqial1m9JxxaKis_2b8GJzpyR2NI8LhZPFXb5T-8DJ1Jw", 
        "RefreshToken": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.kgfFVsor_rztEd7KmBWDd_9VbYAG-aZ_Tk5VBX9kmXee3ih3urmNRuNfs2Hw3NYhuLXqRJngbm5NhIl1Z_-b3A6VpgNWxLo6HhGemklmbnrrqP-SXGRF_Dyfu4CnADTch57LKQzLhCy0aJLE4AS6kZo9a7PdLWGJFSkZBaz1O-PE2oRSoYELMkS9DUQsHT67sBEWI-nxXIsQXj-KCGGBmjJZ9BTdJYUb4nWS3tOGU30OMbfYeTAqHMUVJm5SI9BC-ptnRj2P6uQ1ilZz6rHo7BjPgVTMevptAaHwO9xYSeFOSU9SQeUTlVc690qPEqRNgV_SdTaolu30VPqROZkhIQ.W6RfvwSCJMGzNgmI.PidWCSwDwI1Jp8gRvPuBBFXHLeg4nWn0IIKQw-1yFlBpely_uCmcpRa5b1YE5dPDtuRlZCzcAHfYy_CBPTmS1Uui_ea7l96GmGFhfbM_lTu3vcxgCULWAZgWKopC-jk1cqCk2VY_nGekpandGq9A7Nv7aYyu89jwKxfpb_Rg8xyhfYpz3CrQc35W0CqrVVd2G0WWPyWl2XwPnKl1LF8NgzVE4xqfJlVKtS1Kqgc93huVdwqEjwZuBY6tDgpdDJtQcJDEAF_SmXO8RYPCaUoY8TAquSrSRRLlqBLlvN47iyWwLr6NpXNhJgmED8sTi9NP2kcR1Q0nH-XGsrJnEh2cZSesXTVnUOWEs0zeDGP9OuNPqrI7-aO_rlEqnQQUsLMYAJepRYOW40rd3yPEKnaZnnZCgBKOHhoXxfFDCKVHnKR9NHm-ziGuRv-3TrIr7pGNZWI-Ow4KyRZKF5wTz5vDV9ck74PR2M9JR-OOdIcHaCbDWEOm2jBJoK1hu1wFDBoG8_WO9dliEFvtkw2U4JToigadsxKm8ghnsepU0b423P0QLiab0wErAUITZpccebuLmvzZWGkQSMW8o9SERxgXLpNPIEAz6e1XHfaCByEYLfNXKw9TVBeEOQOWVX3wR6eYUSRGtspVSmKFrmGvPARGkiuojmqjy-pvZx7TczVwh0FAHtLWjyxX3wFfRiEaj00zwYncAZluGR2vPqEcW4t20gnbNk65ZQbJkxc4TRYvz5pKThtXPA68xQJ83RNv_542yXBdV_cMnj_NIW4gqTVHfKrjhDINgr4PoeHME_PByrFm2dwNAiTXolCYkw5R4pfKZKdcGo161B3PpdM43B80_r2HtPa92hfVvKtTvOVPVqG7jVZZqOhx-wjSM4jhme6TMK8MF6huZ1W4GKNe2CqeGcsGAxcS1bT-vlJKQgjPw3kxzMY2p4O157teLzbY3pd2lBmNOESDcMiI1r2DZByekeW6wYh813lv9TEYrRIctUB4mJsmrDlHQ3KIyIxjJGwqJ3OQ2AqXztEExz5aAfovfCcLnFlTDFMVE7Lamu10h_8Zmn-42lU4Rxj49xyQ8xdS8GvLHzCVIsR7Rkp5nKDaMe-9d9oDGPmPD1a7QVr4z2EBoLCH2NidUvkxgxVgjOovc_-wOKOlC_cUmocpK4WrbsXrkxe8iz6EbOqVTUHvfYA0UlXTQdw2TVx68EN6Ts5yMtlzlcWPq9TePR-xtOwP35MQtvHTs-vrQMurDYN2StGy_rlNli8G2Wi0wKG9R9hkiiSXEmVrhRTGJi-zpIu4rG0.pB5sY6GN0fUMJsFLEIuEWQ", 
        "TokenType": "Bearer", 
        "AccessToken": "eyJraWQiOiJIeHJhTTlvdThuMHdPakxtb0FDM3F5ZWowNFNKbyt6UEk4bXN3Qmt1enRnPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJlMTA1ZDJkYy00Zjc2LTRhZDEtOTllMC01ZDU1YmUzNjk4NmEiLCJjb2duaXRvOmdyb3VwcyI6WyJleGdyb3VwIl0sImV2ZW50X2lkIjoiZmQyNWZmZjYtZjIwZS0xMWU4LTgxZjAtYmZmNTI2YjU3MWZkIiwidG9rZW5fdXNlIjoiYWNjZXNzIiwic2NvcGUiOiJhd3MuY29nbml0by5zaWduaW4udXNlci5hZG1pbiIsImF1dGhfdGltZSI6MTU0MzMwMDY4MiwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLmFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb21cL2FwLW5vcnRoZWFzdC0xX0F0UnNNRlRxMCIsImV4cCI6MTU0MzMwNDI4MiwiaWF0IjoxNTQzMzAwNjgyLCJqdGkiOiI4NDFlYTRiMS00MTJmLTQyMWMtODVhZi1kY2M3N2E4MWZiZjkiLCJjbGllbnRfaWQiOiIzaTY3bmE1Nmd2ZWlpN3FrZjBsaWtzcHRsOCIsInVzZXJuYW1lIjoiZXh1c2VyIn0.NoY2xt4Eed8OtwsASIM8wBUHk1BEOsMf1nh0lbo68Vz0MGigYe5m-y9bl8rrdyYFkVdnnbQeCsx8V3kHLfaFI70FnYy42lyu6Nwq48tiheRKyraEeHR6vuf6j8J7AklsMraU9mDUTCfvgitQjhmQn2OMK1qwZaaDYkKPS9ICCh-CsHzXDaETb-wnIgtYGG7u6h0geyONdb4Yr2Y92ACYDKTo_LjG6U8xhdTZJafUWu0EyI1QPpi5BA80FtS2Gg6tt04lsrdc3jo4qZJ6dGXyvmdoMGVyjehzRtg13syi9t0twSWCVF7LB31JJKduA5cBR7vwONNSbp8Y4vw45KRM9g"
    }, 
    "ChallengeParameters": {}
}

AWSコンソールでユーザのステータスを確認して、CONFIRMED になっていたらOKです。

20181127-05.png

3. API動作確認

3.1 クライアントアプリケーションの用意

APIを実行するアプリケーションをPythonで作成しました。
ソースコードは以下の通り。

api_request.py
import requests
import boto3
from warrant.aws_srp import AWSSRP
from requests_aws_sign import AWSV4Sign

USERNAME = 'exuser'
PASSWORD = 'Passw0rd!'
ACCOUNT_ID = "123456789012"
REGION = "ap-northeast-1"
API_ENDPOINT = "https://2037kuo2d5.execute-api.ap-northeast-1.amazonaws.com/stage1"
USER_POOL_ID = "ap-northeast-1_AtRsMFTq0"
CLIENT_ID = "3i67na56gveii7qkf0liksptl8"
IDENTITY_POOL_ID = "ap-northeast-1:e0794416-2b06-4abc-907e-130a4ac2b89f"

def main():
    aws = AWSSRP(username=USERNAME, password=PASSWORD, pool_id=USER_POOL_ID, client_id=CLIENT_ID)
    tokens = aws.authenticate_user()
    id_token = tokens['AuthenticationResult']['IdToken']

    logins = {'cognito-idp.' + REGION + '.amazonaws.com/' + USER_POOL_ID : id_token}
    client = boto3.client('cognito-identity', region_name=REGION)
    cognito_identity_id = client.get_id(
        AccountId=ACCOUNT_ID,
        IdentityPoolId=IDENTITY_POOL_ID,
        Logins=logins
    )
    id_credentials = client.get_credentials_for_identity(
        IdentityId=cognito_identity_id['IdentityId'],
        Logins=logins
    )

    session = boto3.session.Session(
        aws_access_key_id=id_credentials['Credentials']['AccessKeyId'],
        aws_secret_access_key=id_credentials['Credentials']['SecretKey'],
        aws_session_token=id_credentials['Credentials']['SessionToken'],
        region_name=REGION
    )
    credentials = session.get_credentials()
    service = 'execute-api'
    auth = AWSV4Sign(credentials, REGION, service)

    headers = {"Content-Type": "application/json"}
    request_path = '/'
    url = API_ENDPOINT + request_path
    response = requests.request('GET', url, auth=auth, headers=headers)

    print('GET', request_path, response.status_code)
    print(response.headers)
    print(response.text)

    return

if __name__ == '__main__':
    main()

AWSSRP は、 以下のようにしてインストールします。

$ pip install warrant

AWSV4Sign は、GitHUB から取得して、 requests-aws-sign ディレクトリを上記の api_request.py と同じ場所に格納します。

3.2 API実行

APIを実行してみます。
うまくいけば、以下のように表示されます。
レスポンスステータスが200で、APIGatewayのMOCで定義したレスポンス(JSON)が表示されます。

$ python ./api_request.py
('GET', '/', 200)
{'x-amzn-RequestId': '895b2387-f210-11e8-a22c-8f58e6559e46', 'Content-Length': '26', 'x-amz-apigw-id': 'RAlTgH3_NjMFpFQ=', 'Connection': 'keep-alive', 'Date': 'Tue, 27 Nov 2018 06:49:07 GMT', 'Content-Type': 'application/json'}
{
  "message": "hello!"
}
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away