AWS LambdaでAmazon Cognitoユーザの作成とログインを行う

AWS

AWS Lambda(python)を使い、Amazon Cognitoのユーザ作成とログイン処理を行ってみます。
以下の2つの機能を作成します。
1. Lambdaからユーザープールのユーザを作成する
2. ログイン処理。S3(HTMLログイン画面) -> APIGateway -> Lambda(ログイン処理)

Amazon Cognitoの概要

  • Amazon CognitoはモバイルアプリやWebアプリに認証機能を提供するサービス。
  • ユーザープールとIDプールのコンポーネントを提供。
  • ユーザプールはIDやパスワードの認証情報を管理する「ユーザディレクトリ」を提供し、それらの情報を利用して「認証」を行う機能
  • IDプールはAWSリソースへの一時的なAWS認証情報を提供し「認可」を行う機能
  • ユーザープールではサービスへのアクセスを行うための「ユーザープールトークン」を発行
  • ユーザープールは、Facebook、Google、Appleなどの外部のIDプロバイダー(IdP)と連携やSAML認証なども利用出来る.
  • ユーザプールの「ユーザディレクトリ」は指定したリージョンに作成される。耐障害性としてリージョンを超えた要件がある場合は独自でバックアップする必要がある

ユーザープールトークン

以下の3つのトークンが発行される。形式はJWT (JSON Webトークン)

名称概要・用途有効期限
IDトークン認証されたユーザーのIDに関する情報が含まれ、アプリケーションの認証にも利用出来る。5 分~ 1 日間の任意の値
アクセストークン認証されたユーザーの情報が含まれる。ユーザープールの情報更新に利用。5 分~ 1 日間の任意の値
更新トークン新しいIDトークン、アクセストークンを取得するために利用60 分~10 年の任意の値

Cognitoの認証フロー

Cognitoの認証フローは以下のようにいくつか種類があります。

※BlackBelt資料より抜粋

今回はサーバー側で管理APIを使用し、シンプルな「ADMIN_USER_PASSWORD_AUTH」フローで実装します。
完全にサーバ側にもパスワードを不明にする場合は「USER_SRP_AUTH」を利用しますが
その場合、「SRP_A」の計算が必要になるなど処理が若干複雑になります。
「SRP_A」の計算にはAmplifyのライブラリ(AuthenticationHelper.js)が公開されています。必要な場合は以下を参照してください。
Amazon Cognito のユーザープールで記憶済みデバイスを使用するにはどうすればよいですか?

Cognitoユーザプールの作成

AWSコンソールで作成。
今回は簡単なサンプルなのでステップに従い任意の設定を行えば良いですが
以下の点だけ気をつけてください。
・アプリクライアントを作成する
・アプリクライアントのシークレットは設定しない
・アプリクライアントの設定で「認証用の管理 API のユーザー名パスワード認証を有効にする」にチェックする

AWS LambdaでCognitoのユーザ作成

ユーザ「japan」をユーザプールに作成するLambda関数です。
AWSコンソールで直接実行してユーザを作成します。

import json
import boto3
import botocore

import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):

    client = boto3.client('cognito-idp', region_name='ap-northeast-1')
    user_poolid = 'yourUserPoolId'
    user_name = 'japan'
    password = 'yourPassword'

    try:
        # ユーザの作成
        response = client.admin_create_user(
            UserPoolId=user_poolid,
            Username=user_name,
            # メッセージ送信は抑止
            MessageAction='SUPPRESS',
            # 一時パスワードは未設定(次の処理で永続的なパスワードを)
            #TemporaryPassword = '',
        )

        # パスワードの設定
        # Permanentで永続的なパスワードに設定する。
        response = client.admin_set_user_password(
            UserPoolId=user_poolid,
            Username=user_name,
            Password=password,
            Permanent=True,
        )

        return {
            'statusCode': 200,
            'body': json.dumps(response)
        }

    except botocore.exceptions.ClientError as error:
        if error.response['Error']['Code'] == 'UsernameExistsException':
            logger.info('username is already exists.')
            return {
                'statusCode': 200,
                'body': json.dumps('username is already exists.')
            }
        else:
            raise error

ログイン処理用のLambda関数

POSTでユーザ名とパスワードを受け取りCognito認証を行います。
適当なAPIGatewayを作成し、Lambdaプロキシ統合でこの関数を呼び出すように設定します。

import json
import boto3
import logging
import botocore

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):

    # ユーザプールID
    user_poolid = 'yourUserPoolId'
    # クライアントID
    client_id = 'yourCliendId'
    # アカウント情報
    username = event['userId']
    userpass = event['userPassword']

    try:
        client = boto3.client('cognito-idp', region_name='ap-northeast-1')

        response = client.admin_initiate_auth(
            UserPoolId = user_poolid,
            ClientId = client_id,
            AuthFlow = "ADMIN_USER_PASSWORD_AUTH",
            AuthParameters = {
                "USERNAME": username,
                "PASSWORD": userpass,
            }
        )

        logger.info(response["AuthenticationResult"]["AccessToken"])
        logger.info(response["AuthenticationResult"]["RefreshToken"])
        logger.info(response["AuthenticationResult"]["IdToken"])

        return {
            'statusCode': 200,
            'headers': {
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
            },
            'body': json.dumps(response)
        }

    except botocore.exceptions.ClientError as error:
        if error.response['Error']['Code'] == 'UserNotFoundException':
            logger.info('user not found.')
            return {
                'statusCode': 200,
                'body': json.dumps('user not found.')
            }
        else:
            raise error

サインイン画面

ユーザ名とパスワードを入力する単純な画面です。S3で静的ウェブサイトホスティングを有効にしオブジェクトを公開します。
ボタンを押下するとログイン用のAPIGatewayを呼び出し、APIGateway経由でLambdaを実行します。

<!DOCTYPE html>
<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <meta charset="UTF-8" />
    <title>Cognito SignIn</title>

    <script type="text/javascript">
    $(function(){
      $("#signInButton").click( function(){
        // APIGatewayのURL
        var url = "https://<endpoint>/app/login";
        var JSONdata = {
          "userId": $("#userId").val(),
          "userPassword": $("#userPassword").val(),
        };

        $.ajax({
          type : 'post',
          url : url,
          data : JSON.stringify(JSONdata),
          contentType: 'application/json',
          dataType : 'json',
          scriptCharset: 'utf-8',
          success : function(data) {
            $("#response").css("color","black");
            $("#response").html(JSON.stringify(data));
          },
          error : function(data) {
            $("#response").css("color","red");
            $("#response").html(JSON.stringify(data));
          }
        });
      })
    })
</script>
</head>
  <body>
    <h2>Cognito SignIn</h2>
    <p>User: <input size="30" type="text" id="userId"></p>
    <p>Password: <input size="30" type="password" id="userPassword"></p>
    <textarea id="response" cols=120 rows=10 disabled></textarea>
    <p><button id="signInButton">SignIn</button></p>
  </body>
</html>

参考資料

以上

タイトルとURLをコピーしました