Authorization sử dụng Amazon Cognito, API Gateway và IAM (Phần 2)

Lời nói đầu

Chào các bạn, mình là Duy Nam - Solution Architect Engineer VTI Japan. Tiếp theo Authorization sử dụng Amazon Cognito, API Gateway và IAM (Phần 1) hôm nay mình xin giới thiệu về tiếp về việc thêm chức năng Authorization vào API Gateway.

III. Thêm chức năng Authorizer vào API Gateway

1. Tạo Amazon Cognito Resource và đăng ký User

  • Overview
    Cognito

2. Sử dụng Cloudformation để triển khai Amazon Cognito

  • api-with-auth.yaml
...

#===========================================================================================#
# Resource Block
#===========================================================================================#
Resources:
  #================================================================#
  # Create Cognito Resource
  #================================================================#
  #----------------------------------------------#
  # Cognito User Pool
  #----------------------------------------------#
  CognitoUserPool:
    Type: 'AWS::Cognito::UserPool'
    Properties:
      UserPoolName: CognitoPool

  #----------------------------------------------#
  # Cognito User Pool Group 
  #----------------------------------------------#
  CognitoUserPoolGroup:
    DependsOn:
      - CognitoUserPool
    Type: 'AWS::Cognito::UserPoolGroup'
    Properties:
      GroupName: pet-veterinarian
      UserPoolId: !Ref CognitoUserPool

  #----------------------------------------------#
  # Cognito User Pool Client 
  #----------------------------------------------#
  CognitoUserPoolClient:
    DependsOn:
      - CognitoUserPool
    Type: 'AWS::Cognito::UserPoolClient'
    Properties:
      UserPoolId: !Ref CognitoUserPool
      AllowedOAuthFlows:
        - implicit
      AllowedOAuthFlowsUserPoolClient: true
      AllowedOAuthScopes:
        - email
        - openid
      CallbackURLs:
        - 'http://localhost'
      GenerateSecret: false
      ExplicitAuthFlows:
        - ALLOW_USER_PASSWORD_AUTH
        - ALLOW_USER_SRP_AUTH
        - ALLOW_REFRESH_TOKEN_AUTH
      SupportedIdentityProviders:
        - COGNITO

  #----------------------------------------------#
  # Cognito User Pool Domain 
  #----------------------------------------------#
  CognitoUserPoolDomain:
    DependsOn:
      - CognitoUserPoolClient
    Type: 'AWS::Cognito::UserPoolDomain'
    Properties:
      # using client id will make the domain unique
      Domain: !Sub dns-name-${CognitoUserPoolClient}
      UserPoolId: !Ref CognitoUserPool

  #----------------------------------------------#
  # Helper Cognito Lambda Role
  #----------------------------------------------#
  HelperCognitoLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: helperCognitoLambdaRole
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - cognito-idp:Admin*
                Resource: !GetAtt CognitoUserPool.Arn
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-HelperInitCognitoFunction-*:*

  #----------------------------------------------#
  # PetAPI Lambda LogGroup
  #----------------------------------------------#
  HelperInitCognitoFunctionLogGr:
    Type: AWS::Logs::LogGroup
    Properties: 
      LogGroupName: !Sub /aws/lambda/HelperInitCognitoFunction
      RetentionInDays: 7

  #----------------------------------------------#
  # Helper Cognito Lambda Init
  #----------------------------------------------#
  HelperInitCognitoFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: HelperInitCognitoFunction
      Code:
        ZipFile: >
          const AWS = require("aws-sdk");
          const response = require("cfn-response");
          const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider({apiVersion: '2016-04-18'});

          exports.handler = function (event, context, callback) {
              var userPoolId = event.ResourceProperties.UserPoolId;
              var username = event.ResourceProperties.CognitoUserName;
              var pass = event.ResourceProperties.CognitoUserPassword;

              console.log("username: " + username);

              var params = {
                  UserPoolId: userPoolId,
                  Username: username,
                  TemporaryPassword: pass
              };

              cognitoidentityserviceprovider.adminCreateUser(params, function (err, data) {
                  if (err) {
                      console.log(err, err.stack);
                  } else {
                      console.log(data);
                  }

                  const params = {
                      UserPoolId: userPoolId,
                      Username: username,
                      Password: pass,
                      Permanent: true
                  };
                  cognitoidentityserviceprovider.adminSetUserPassword(params, function (err, data) {
                      if (err) {
                          response.send(event, context, "FAILED", {});
                      } else {
                          response.send(event, context, "SUCCESS", {});
                      }
                  });
              });
          };
      Handler: index.handler
      Role: !GetAtt HelperCognitoLambdaRole.Arn
      Runtime: nodejs12.x
      Timeout: 30

  #----------------------------------------------#
  # Helper Cognito Lambda Invoke
  #----------------------------------------------#
  HelperInitializeCognitoUser:
    Type: Custom::HelperInitCognitoFunction
    DependsOn: CognitoUserPool
    Properties:
      ServiceToken: !GetAtt HelperInitCognitoFunction.Arn
      UserPoolId: !Ref CognitoUserPool
      CognitoUserName: !Ref CognitoUserName
      CognitoUserPassword: !Ref CognitoUserPassword
  # <<< helper to create Cognito User without the need to confirm email

  #----------------------------------------------#
  # Cognito User Pool User To Group Attachment
  #----------------------------------------------#
  CognitoUserPoolUserToGroupAttachment:
    DependsOn:
      - CognitoUserPoolGroup
      - HelperInitializeCognitoUser
    Type: 'AWS::Cognito::UserPoolUserToGroupAttachment'
    Properties:
      GroupName: !Ref CognitoUserPoolGroup
      Username: !Ref CognitoUserName
      UserPoolId: !Ref CognitoUserPool

  #================================================================#
  # PetAPI Resource
  #================================================================#
  #----------------------------------------------#
  # IAM Policy for PetAPI Lambda
  #----------------------------------------------#
  ApiServiceIAMPolicy:
    Type: 'AWS::IAM::Policy'
    Properties:
      Roles:
        - !Ref ApiServiceIAMRole
      PolicyName: ApiServiceIAMPolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - cognito-idp:Admin*
            Resource: !GetAtt CognitoUserPool.Arn
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/ApiServiceLambdaFunction:*
...
  • Mình có thêm một Lambda tiến hành tự động đăng ký user vào trong Amazon Cognito khi chạy cloudformation một user/password sẽ được tạo ra thông qua parameter truyền vào

3. Tạo Lambda-Authorizer

  • Tạo requirements.txt
python-jose==3.2.0
  • Lambda source code (lambda.py)
import os
import boto3
import json
import time
import urllib.request
from jose import jwk, jwt
from jose.utils import base64url_decode

# envs
TABLE_NAME = os.environ['TABLE_NAME']
AWS_REGION = os.environ['AWS_REGION']
COGNITO_USER_POOL_ID = os.environ['COGNITO_USER_POOL_ID']
COGNITO_APP_CLIENT_ID = os.environ['COGNITO_APP_CLIENT_ID']

keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(AWS_REGION, COGNITO_USER_POOL_ID)
# instead of re-downloading the public keys every time
# we download them only on cold start
# https://aws.amazon.com/blogs/compute/container-reuse-in-lambda/
with urllib.request.urlopen(keys_url) as f:
    response = f.read()
keys = json.loads(response.decode('utf-8'))['keys']

def handler(event, context):
    print(event)

    token_data = parse_token_data(event)
    if token_data['valid'] is False:
        return get_deny_policy()

    try:
        claims = validate_token(token_data['token'])
        groups = claims['cognito:groups']

        results = batch_query_wrapper(TABLE_NAME, 'group', groups)
        print(results)

        if len(results) > 0:
            policy = {
                'Version': results[0]['policy']['Version'],
                'Statement': []
            }
            for item in results:
                policy['Statement'] = policy['Statement'] + item['policy']['Statement']

            return get_response_object(policy)

        return get_deny_policy()

    except Exception as e:
        print(e)

    return get_deny_policy()

def get_response_object(policyDocument, principalId='yyyyyyyy', context={}):
    return {
        "principalId": principalId,
        "policyDocument": policyDocument,
        "context": context,
        "usageIdentifierKey": "{api-key}"
    }

def get_deny_policy():
    return {
        "principalId": "yyyyyyyy",
        "policyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "execute-api:Invoke",
                    "Effect": "Deny",
                    "Resource": "arn:aws:execute-api:*:*:*/ANY/*"
                }
            ]
        },
        "context": {},
        "usageIdentifierKey": "{api-key}"
    }

def batch_query_wrapper(table, key, values):
    results = []

    dynamodb = boto3.resource('dynamodb', region_name=AWS_REGION)
    values_list = [values[x:x + 25] for x in range(0, len(values), 25)]

    for vlist in values_list:
        response = dynamodb.batch_get_item(RequestItems={table: {'Keys': [{key: val} for val in vlist]}})
        results.extend(response['Responses'][table])

        while response['UnprocessedKeys']:
            response = dynamodb.batch_get_item(RequestItems={table: {'Keys': [{key: val} for val in vlist]}})
            results.extend(response['Response'][table])

    return results

def parse_token_data(event):
    response = {'valid': False}

    if 'Authorization' not in event['headers']:
        return response

    auth_header = event['headers']['Authorization']
    auth_header_list = auth_header.split(' ')

    # deny request of header isn't made out of two strings, or
    # first string isn't equal to "Bearer" (enforcing following standards,
    # but technically could be anything or could be left out completely)
    if len(auth_header_list) != 2 or auth_header_list[0] != 'Bearer':
        return response

    access_token = auth_header_list[1]
    return {
        'valid': True,
        'token': access_token
    }

def validate_token(token):
    # get the kid from the headers prior to verification
    headers = jwt.get_unverified_headers(token)
    kid = headers['kid']

    # search for the kid in the downloaded public keys
    key_index = -1
    for i in range(len(keys)):
        if kid == keys[i]['kid']:
            key_index = i
            break

    if key_index == -1:
        print('Public key not found in jwks.json')
        return False

    # construct the public key
    public_key = jwk.construct(keys[key_index])

    # get the last two sections of the token,
    # message and signature (encoded in base64)
    message, encoded_signature = str(token).rsplit('.', 1)

    # decode the signature
    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))

    # verify the signature
    if not public_key.verify(message.encode("utf8"), decoded_signature):
        print('Signature verification failed')
        return False

    print('Signature successfully verified')

    # since we passed the verification, we can now safely
    # use the unverified claims
    claims = jwt.get_unverified_claims(token)

    # additionally we can verify the token expiration
    if time.time() > claims['exp']:
        print('Token is expired')
        return False

    # and the Audience  (use claims['client_id'] if verifying an access token)
    if claims['client_id'] != COGNITO_APP_CLIENT_ID:
        print('Token was not issued for this audience')
        return False

    # now we can use the claims
    print(claims)
    return claims
  • Tiến hành Package Lambda
cd ./Lambda/custom-auth
pip3 install --target ./package python-jose==3.2.0
cd ./package
zip -r ../custom-auth.zip . > /dev/null
cd .. && zip -g custom-auth.zip lambda.py
mv ./custom-auth.zip ../../cf-lambdas
rm -r ./package
cd ./../..
  • Upload source lên S3
aws s3 cp ./cf-lambdas/custom-auth.zip s3://$S3_BUCKET_NAME

4. Sử dụng Cloudformation để triển khai Amazon DynamoDB, Lambda-Authorizer

4.1 OverView

4.2 Update API Gateway resource sử dụng Cognito tiến hành Protect API

...
  #================================================================#
  # API Gateway Resource
  #================================================================#
  #----------------------------------------------#
  # Create API Gateway with RestApi type
  #----------------------------------------------#
  ApiGatewayRestApi:
    Type: 'AWS::ApiGateway::RestApi'
    Properties:
      Name: "MyApiGateway"

  #----------------------------------------------#
  # Create API Gateway Resource
  #----------------------------------------------#
  ApiGatewayResource:
    Type: 'AWS::ApiGateway::Resource'
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      ParentId: !GetAtt ApiGatewayRestApi.RootResourceId
      PathPart: "{api+}"

  #----------------------------------------------#
  # API Gateway Invoke Lambda Policy
  #----------------------------------------------#
  ApiGatewayCustomAuthIAMPolicy:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: ApiGatewayCustomAuthIAMPolicy
      Roles:
        - !Ref ApiGatewayCustomAuthIAMRole
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - 'lambda:InvokeFunction'
            Resource: !GetAtt CustomAuthLambdaFunction.Arn

  #----------------------------------------------#
  # API Gateway Invoke Lambda Role
  #----------------------------------------------#
  ApiGatewayCustomAuthIAMRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: ApiGatewayCustomAuthIAMRole
      AssumeRolePolicyDocument: |-
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": "sts:AssumeRole",
              "Principal": {
                "Service": "apigateway.amazonaws.com"
              },
              "Effect": "Allow",
              "Sid": ""
            }
          ]
        }

  #----------------------------------------------#
  # API Gateway Authorizer
  #----------------------------------------------#
  ApiGatewayAuthorizer:
    Type: 'AWS::ApiGateway::Authorizer'
    Properties:
      Name: custom-auth
      RestApiId: !Ref ApiGatewayRestApi
      Type: REQUEST
      IdentitySource: method.request.header.Authorization
      AuthorizerResultTtlInSeconds: '300'
      AuthorizerCredentials: !GetAtt ApiGatewayCustomAuthIAMRole.Arn
      AuthorizerUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${CustomAuthLambdaFunction.Arn}/invocations

  #----------------------------------------------#
  # Create API Gateway Method
  #----------------------------------------------#
  ApiGatewayMethod:
    DependsOn: ApiGatewayAuthorizer
    Type: 'AWS::ApiGateway::Method'
    Properties:
      HttpMethod: "ANY"
      ResourceId: !Ref ApiGatewayResource
      RestApiId: !Ref ApiGatewayRestApi
      AuthorizationType: CUSTOM
      AuthorizerId: !Ref ApiGatewayAuthorizer
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: "POST"
        Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiServiceLambdaFunction.Arn}/invocations

  #----------------------------------------------#
  # Deploy API Gateway
  #----------------------------------------------#
  ApiGatewayDeploymentProtected:
    DependsOn:
      - ApiGatewayAuthorizer
      - ApiGatewayMethod
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      StageName: dev
      Description: protected api
...

4.3 Tạo Custom-Auth Lambda

...

  #================================================================#
  # Custom-Auth Resource
  #================================================================#
  #----------------------------------------------#
  # IAM Policy for Custom-Auth Lambda
  #----------------------------------------------#
  CustomAuthIAMPolicy:
    Type: 'AWS::IAM::Policy'
    Properties:
      Roles:
        - !Ref CustomAuthIAMRole
      PolicyName: CustomAuthIAMPolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - cognito-idp:*
            Resource: !GetAtt CognitoUserPool.Arn
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/CustomAuthLambdaFunction:*
          - Effect: Allow
            Action:
              - dynamodb:BatchGetItem
            Resource: !GetAtt DynamoDBTable.Arn

  #----------------------------------------------#
  # IAM Role for Custom-Auth Lambda
  #----------------------------------------------#
  CustomAuthIAMRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: CustomAuthIAMRole
      AssumeRolePolicyDocument: |-
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": "sts:AssumeRole",
              "Principal": {
                "Service": "lambda.amazonaws.com"
              },
              "Effect": "Allow",
              "Sid": ""
            }
          ]
        }

  #----------------------------------------------#
  # Custom-Auth Lambda LogGroup
  #----------------------------------------------#
  CustomAuthLambdaLogGr:
    Type: AWS::Logs::LogGroup
    Properties: 
      LogGroupName: !Sub /aws/lambda/CustomAuthLambdaFunction
      RetentionInDays: 7

  #----------------------------------------------#
  # Custom-Auth Lambda
  #----------------------------------------------#
  CustomAuthLambdaFunction:
    DependsOn:
      - CustomAuthLambdaLogGr
    Type: 'AWS::Lambda::Function'
    Properties:
      FunctionName: CustomAuthLambdaFunction
      Runtime: "python3.6"
      Handler: "lambda.handler"
      Role: !GetAtt CustomAuthIAMRole.Arn
      Code:
        S3Bucket: !Sub cognito-api-gateway-${AWS::AccountId}-${AWS::Region}-lambdas
        S3Key: "custom-auth.zip"
      Environment:
        Variables:
          TABLE_NAME: "auth-policy-store"
          COGNITO_USER_POOL_ID: !Ref CognitoUserPool
          COGNITO_APP_CLIENT_ID: !Ref CognitoUserPoolClient
...

4.4 Tạo DynamoDB Resource và Lambda Init giá trị của DynamoDB

...

  #================================================================#
  # DynamoDB Resource
  #================================================================#
  #----------------------------------------------#
  # DynamoDB Table
  #----------------------------------------------#
  DynamoDBTable:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: auth-policy-store
      AttributeDefinitions:
        - AttributeName: "group"
          AttributeType: "S"
      KeySchema:
        - AttributeName: "group"
          KeyType: "HASH"
      ProvisionedThroughput:
        ReadCapacityUnits: "5"
        WriteCapacityUnits: "5"

  #----------------------------------------------#
  # Helper DynamoDb Lambda Role
  #----------------------------------------------#
  HelperDynamoDbLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: dynamodbAccessRole
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:PutItem
                Resource: !GetAtt DynamoDBTable.Arn
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Sub arn:aws:logs:${AWS::Region}::log-group:/aws/lambda/${AWS::StackName}-*:*

  #----------------------------------------------#
  # Helper DynamoDb Lambda LogGroup
  #----------------------------------------------#
  HelperDynamoDbInitLambdaLogGr:
    Type: AWS::Logs::LogGroup
    Properties: 
      LogGroupName: !Sub /aws/lambda/HelperDynamoDbInitFunction
      RetentionInDays: 7

  #----------------------------------------------#
  # Helper DynamoDb Lambda Init
  #----------------------------------------------#
  HelperDynamoDbInitFunction:
    DependsOn:
      - HelperDynamoDbInitLambdaLogGr
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: HelperDynamoDbInitFunction
      Code:
        ZipFile: >
          const AWS = require("aws-sdk");
          const response = require("cfn-response");
          const docClient = new AWS.DynamoDB.DocumentClient();
          exports.handler = function(event, context) {
              console.log(JSON.stringify(event,null,2));
              var params = {
                TableName: event.ResourceProperties.DynamoTableName,
                Item:{
                       "group": "pet-veterinarian",
                       "policy": {
                         "Statement": [
                           {
                             "Action": "execute-api:Invoke",
                             "Effect": "Allow",
                             "Resource": [
                               "arn:aws:execute-api:*:*:*/*/*/petstore/v1/*",
                               "arn:aws:execute-api:*:*:*/*/GET/petstore/v2/status"
                             ],
                             "Sid": "PetStore-API"
                           }
                         ],
                         "Version": "2012-10-17"
                       }
                     }
            };
          docClient.put(params, function(err, data) { if (err) {
            response.send(event, context, "FAILED", {});
          } else {
            response.send(event, context, "SUCCESS", {});
          }
          });
          };
      Handler: index.handler
      Role: !GetAtt HelperDynamoDbLambdaRole.Arn
      Runtime: nodejs12.x
      Timeout: 30

  #----------------------------------------------#
  # Helper DynamoDb Lambda Invoke
  #----------------------------------------------#
  HelperDynamoDbInitializeDynamoDB:
    Type: Custom::InitFunction
    DependsOn: DynamoDBTable
    Properties:
      ServiceToken: !GetAtt HelperDynamoDbInitFunction.Arn
      DynamoTableName: !Ref DynamoDBTable

#===========================================================================================#
# Output Block
#===========================================================================================#
Outputs:
  CognitoUserPoolClientId:
    Value: !Ref CognitoUserPoolClient
  CognitoHostedUiUrl:
    Value: !Sub https://${CognitoUserPoolDomain}.auth.${AWS::Region}.amazoncognito.com/login?client_id=${CognitoUserPoolClient}&response_type=token&scope=email+openid&redirect_uri=http://localhost
  ApiGatewayDeploymentUrlApiEndpoint:
    Value: !Sub https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev/petstore/v1/pets
  ApiGatewayDeploymentUrlApiEndpointV2:
    Value: !Sub https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev/petstore/v2/pets

4.5 Tạo Service sử dụng Cloudformation

  • Sau khi tổng hợp các nội dung Cloudformation phía trên các bạn chạy lại để tiến hành update resource
CF_STACK_NAME="cognito-api-gateway"
S3_BUCKET_NAME="${CF_STACK_NAME}-${ACCOUNT_ID}-${STACK_REGION}-lambdas"

aws cloudformation deploy --template-file ./infrastructure/api-with-auth.yaml \
     --stack-name $CF_STACK_NAME \
     --s3-bucket $S3_BUCKET_NAME \
     --s3-prefix cfn \
     --parameter-overrides CognitoUserName=cognitouser CognitoUserPassword=Password@1990 \
     --capabilities CAPABILITY_NAMED_IAM
  • Sử dụng Postman test service xuất hiện thông báo Unauthorized

5. Để tạo một request thành công đến phía service bạn cần phải làm theo từng bước sau:

5.1 Có một user name và password để chứng thực tại Amazon Cognito user pool.
5.2 Nhận lấy tokens (id token, access token, and refresh token) dựa trên thông tin user/password.
  • Các bạn có thể tham khảo thông tin trong file get-token.sh dưới đây
#!/usr/bin/env bash

CF_STACK_NAME="cognito-api-gateway"
ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
STACK_REGION=$(aws configure get region)

get_login_payload_data() {
  OUTPUT=($(aws cloudformation describe-stacks \
        --stack-name ${CF_STACK_NAME} \
        --query 'Stacks[0].[Parameters[0:2].ParameterValue, Outputs[1].OutputValue, Outputs[0].OutputValue] | []' \
        --output text))
  COGNITO_USERS_PASSWORD="${OUTPUT[0]}"
  COGNITO_USERNAME="${OUTPUT[1]}"
  API_URL="${OUTPUT[2]}"
  COGNITO_CLIENT_ID="${OUTPUT[3]}"
  DATA=$(cat<<EOF
{
"AuthParameters" : {
    "USERNAME" : "${COGNITO_USERNAME}",
    "PASSWORD" : "${COGNITO_USERS_PASSWORD}"
},
"AuthFlow" : "USER_PASSWORD_AUTH",
"ClientId" : "${COGNITO_CLIENT_ID}"
}
EOF)
  echo $DATA
}

get_access_token() {
  ACCESS_TOKEN=$(curl -s -X POST --data "${DATA}" \
    -H 'X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth' \
    -H 'Content-Type: application/x-amz-json-1.1' \
    https://cognito-idp."${STACK_REGION}".amazonaws.com/ | cut -d':' -f 3 | cut -d'"' -f 2)
  echo $ACCESS_TOKEN
}

get_login_payload_data
get_access_token
  • Chạy get-token.sh và thu thấy access token
bash ./source/get-token.sh
5.3 Tạo một HTTPS (TLS) request đến API Gateway và truyền access token vào headers.
  • Tại Headers thêm giá trị:

    • Key: Authorization
    • Value: Bearer eyJraWQiOiJReDB6SkMy...(Access Token)
  • Thử gọi api-v1

    • Trong lần gọi này đã nhận được data từ API service. Chúng ta hãy xem các bước đã được thực thi:
      1. Lambda authorizer tiến hành validate access token
      2. Lambda authorizer xác định policy trong DynamoDB dựa trên group name đã nhận được từ access token
      3. Lambda authorizer trả lại IAM Policy ngược lại cho API Gateway
      4. API Gateway thu lấy IAM Policy và xác nhận các hành động cho phép
      5. API Gateway forward request đến Lambda Service
      6. Lambda trả về một Response
  • Thử gọi api-v2

    • Tại Policy đã định nghĩa trong DynamoDB, chỉ "/petstore/v2/status" API được sử dụng nên response trả về sẽ là unauthorized
    • Sử dụng Policy định nghĩa trong DynamoDB chúng ta có thể giới hạn được quyền của người dùng
  • Thử gọi api-v2 lấy status

    • Access thành công, status trả về ok

4. Xóa Resource

  • Để tiến hành xóa resource đã được tạo ra các bạn sử dụng các lệnh dưới đây:
aws cloudformation delete-stack --stack-name $CF_STACK_NAME
aws s3 rm s3://"${S3_BUCKET_NAME}/custom-auth.zip"
aws s3 rm s3://"${S3_BUCKET_NAME}/pets-api.zip"
aws s3 rm s3://"${S3_BUCKET_NAME}/cfn" --recursive
aws s3api delete-bucket \
    --bucket "${S3_BUCKET_NAME}" \
    --region "${STACK_REGION}" > /dev/null

Kết luận

  • Qua bài viết này hi vọng các bạn có một Overview về việc Authorize sử dụng Congito và API Gateway
  • Mình là Duy Nam - VTI Japan các bạn có câu hỏi gì có thể comment phía dưới
  • Thanks and Best Regaruads

Leave a Reply

Your email address will not be published. Required fields are marked *