Amazon Web Services ブログ

Amazon Lex チャットボットで、ワンタイムパスワードを使ってユーザーを認証する

現在、多くの企業がワンタイムパスワード (OTP) を使って、ユーザーを認証しています。アプリケーションを続行するには、パスワードの入力を求められます。このパスワードはテキストメッセージを介して登録済みの電話番号に送信され、このパスワードを入力すると認証が行われます。ユーザー ID を検証するには、簡単で安全なアプローチです。このブログ投稿では、同じ OTP 機能を Amazon Lex チャットボットに統合する方法について説明します。

Amazon Lex では音声とテキストの両方を使って、既存のアプリケーションにリアルな対話型インターフェイスを簡単に構築できます。

詳細に進む前に、OTP について詳しく見てみましょう。OTP は通常、1 つのログインセッションまたはトランザクションに対してのみ有効な一連の数字です。OTP は一定の期間が経過すると失効します。その後は新しい OTP を生成する必要があります。ウェブ、モバイル、その他のデバイスなどさまざまなチャネルで使用できます。

このブログ投稿では、モバイルデバイスでの料理注文チャットボットの例を使用して、ユーザーを認証する方法をご紹介します。Amazon Lex ボットでは OTP が認証したときだけ、ユーザーの注文が行えるようになります。

OTP を使用した次の会話を考えてみましょう。

先ほど説明したやり取りを実現するために、以下のインテントで最初に出前のボットを構築します。OTP パスワードは OrderFood などのトランザクションを含むインテントで使用します。

OTP をキャプチャする 2 つの実装 (音声によるものと、テキストによるもの) をご紹介します。最初の実装では、OTP は音声またはテキストモダリティとして Amazon Lex が直接キャプチャします。OTP 値はスロット値として Amazon Lex に直接送信されます。2 つ目の実装では、OTP はクライアントアプリケーションが (テキストモダリティを使用して) キャプチャします。クライアントアプリケーションは、クライアントのダイアログボックスから OTP をキャプチャし、それをセッション属性として Amazon Lex に送信します。セッション属性は暗号化できます。

Amazon Lex ランタイムに対して行われるすべての API 呼び出しは、HTTPS を使用して暗号化されることに注意してください。セッション属性を介して使用する場合の OTP の暗号化には、セキュリティのレベルを追加します。Amazon Lex はセッション属性またはスロット値を介して受信した OTP を、OTP を検証できる AWS Lambda 関数に渡します。

アプリケーションアーキテクチャ

ボットには次の AWSの サービスに基づくアーキテクチャがあります。

  • 対話型インターフェイスを構築するための Amazon Lex。
  • データの検証とフルフィルメントを実行する AWS Lambda。
  • データを保存および取得するための Amazon DynamoDB。
  • SMS メッセージを発行する Amazon Simple Notification Service (SNS)。
  • OTP を暗号化および復号化する AWS Key Management Service (KMS)。
  • KMS を使用するための一時的な AWS 認証情報を取得するための Amazon Cognito ID プール。

次の図は、さまざまなサービスがどのように連携するかを示しています。

音声モダリティを使用した OTP のキャプチャ

ユーザーが最初にボットとの対話を開始すると、ユーザーのメールまたはその他のメタデータがフロントエンドから Amazon Lex ランタイムに渡されます。

AWS Lambda 検証コードフックを使用して、次のタスクを実行します。

  1. AWS Lambda が OTP を生成し、DynamoDB テーブルに保存する。
  2. AWS Lambda が SNS を使用して、OTP をユーザーの携帯電話に送信する。
  3. ユーザーが OTP をクライアントアプリケーションに入力すると、スロットタイプとして Amazon Lex に送信される。
  4. AWS Lambda が OTP を確認し、認証が成功した場合、Amazon Lex に会話を続行するように通知する。

ユーザーが認証されると、ユーザーは Amazon Lex ボットで注文できます。

テキストモダリティを使用した OTP のキャプチャ

最初の実装と同様に、ユーザーのメールまたはその他のメタデータは、フロントエンドから Amazon Lex ランタイムに送信されます。

2 つ目の実装では、AWS Lambda 検証コードフックを使用して、次のタスクを実行します。

  1. AWS Lambda が OTP を生成し、DynamoDB テーブルに保存する。
  2. Lambda が SNS を使用して、OTP をユーザーの携帯電話に送信する。
  3. ユーザーがクライアントアプリケーションのダイアログボックスに OTP を入力する。
  4. クライアントアプリケーションはユーザーが入力した OTP を暗号化し、セッション属性で Amazon Lex ランタイムに送信する。

: セッション属性は暗号化できます。

  1. AWS Lambda が OTP を確認し、認証が成功した場合、Amazon Lex に会話を続行するように通知する。

注: OTP が暗号化されている場合、Lambda 関数は最初に復号化する必要があります。

ユーザーが認証されると、ユーザーは Amazon Lex ボットで注文できます。

OTP の生成

OTPの生成にはいろんな方法があります。この例では、1 分間有効な 6 桁のランダムな数値を OTP として生成し、DynamoDB テーブルに保存します。OTP を確認するには、ユーザーが入力した値と DynamoDB テーブルの値とを比較します。

OTP ボットのデプロイ

こちらの AWS CloudFormation ボタンを使用して、us-east-1 の AWS リージョンで OTP ボットを起動します。

ソースコードは、GitHub リポジトリで入手できます

AWS CloudFormation コンソールを開き、[Parameters] ページで有効な電話番号を入力します。これは OTP の送信先の電話番号です。

[Next] を 2 回クリックして、[Review] ページを表示します。

確認のチェックボックスをクリックし、[Create] を選択して ExampleBot をデプロイします。

CloudFormation スタックは、AWS アカウントに次のリソースを作成します。

  • ExampleBot ウェブ UI をホストする Amazon S3 バケット。
  • 自然言語処理を提供する Amazon Lex ボット。
  • OTP の送信と検証に使用する AWS Lambda 関数。
  • Lambda 関数用 AWS IAM ロール。
  • セッションデータを保存する Amazon DynamoDB テーブル。
  • データを暗号化および復号化するための AWS KMS キー。
  • クライアントを認証し、一時的な AWS 認証情報を提供するよう設定した Amazon Cognito ID プール。

デプロイが完了すると (約 15 分後)、スタックの [Output] タブに以下が表示されます。

  • ExampleBotURL: この URL をクリックして、ExampleBot と対話します

ボットを作成してみましょう。

このブログ投稿は、「Building Better Bots Using Amazon Lex」で説明したボット構築の基本に基づいています。そのブログ投稿からのガイダンスに従って、2 つのインテントを持つ Amazon Lex ボットを作成します。

GetFoodMenu インテントは認証を必要としません。ユーザーはボットにどのような料理アイテムがメニューにあるかを尋ねることができます。

何かおすすめはありますか?

メニューを見せてください。

メニューには何がありますか?

どんな料理がありますか?

ボットは GetFoodMenu インテントが引き出されたときにユーザーが注文できる料理のリストを返します。

ユーザーが何を注文するかすでに分かっている場合、次の入力テキストを使って料理を注文し、OrderFood インテントを呼び出すことができます。

パスタを注文したいのですが。

料理を注文できますか?

チーズバーガー。

Amazon Lex は Lambda コードフックを使用して、ユーザーが認証されているかどうかを確認します。ユーザーが認証されると、Amazon Lex はユーザーの現在の注文に料理を追加します。

ユーザーがまだ認証されていない場合、やり取りは次のようになります。

ユーザー: パスタを注文したいのですが。

ボット: まだ認証が完了していないようです。登録済みの電話番号に OTP を送信しました。OTP を入力してください。

ユーザー: 812734

: ユーザーがテキストモダリティを使用している場合、ユーザーの「宛先」入力を暗号化できます。

OTP をご入力いただきまして、ありがとうございます。ピザを注文しました。他にも何か注文しますか?

ユーザーが認証できない場合、Amazon Lex は多要素認証 (MFA) プロセスに進みます。AWS Lambda はそのユーザーの携帯電話番号と配送先住所について、DynamoDB テーブルをクエリします。DynamoDB が値を返すと、AWS Lambda はユーザーのメタデータに基づいて OTP を生成し、UUID をプライマリキーとして DynamoDB テーブルに保存し、セッション属性に格納します。次に、AWS Lambda は SNS を使用して OTP をユーザーに送信し、OrderFood インテントのピンスロットを使ってピンを引き出します。

ユーザーが OTP を入力すると、Amazon Lex は Lambda コードフックを使用してピンを検証します。AWS Lambda はセッション属性に UUID を使って DynamoDB テーブルをクエリし、OTP を検証します。ピンが正しい場合、Lambda 関数は秘密データを DynamoDB にクエリします。ピンが正しくない場合、Lambda は検証ステップを再度実行します。

実装の詳細

次のテーブルとスクリーンショットは、いろいろなスロットタイプとインテントを示し、AWS マネジメントコンソールを使用して必要なスロットを指定する方法を説明しています。

スロット

スロットタイプ: スロット値
料理 Amazon.Food
ピン Amazon.Number

インテント

インテント名 サンプル発話
OrderFood

{料理} を注文したいのですが

{料理}

{料理} を食べたいです

{料理} を注文する

料理を注文できますか?

{料理} を注文できますか?

GetFoodMenu

何かおすすめはありますか?

メニューを見せてください。

メニューには何がありますか?

どんな料理がありますか?

GetFoodMenu インテントはロジックを実行するための初期化および検証コードフックとして GetMenu Lambda 関数を使用し、OrderFood インテントはロジックを実行するための初期化および検証コードフックとして OrderFood Lambda 関数を使用します。

これらは Lambda 関数が従うステップです。

  1. Lambda 関数は最初にユーザーが呼び出したインテントをチェックします。
  2. ペイロードが GetFoodMenu インテント用である場合:
    1. 最初の Amazon Lex ランタイム API 呼び出しのセッション属性で、クライアントが次のアイテムを送信すると想定しています。Lex コンソールではセッション属性を渡すことができないため、Lambda 関数はセッション属性が空の場合、テスト目的で次​​のセッション属性を作成します。
      {’email’ : ‘user@domain.com’, ‘auth’ : ‘false’, ‘uuid’: None, ‘currentOrder’: None, ‘encryptedPin’: None}

      • ’email’ はユーザーのメールアドレスです。
      • ‘auth’ : ‘false’ はユーザーが認証されていないことを意味します。
      • ‘uuid’は OTP を DynamoDB に保存するためのプライマリキーとして、後で使用するフラグです。
      • ‘currentOrder’ はユーザーが注文した料理アイテムを追跡します。
      • ‘encryptedPin’ は暗号化した OTP を送信するため、フロントエンドクライアントが使用します。実装で OTP を暗号化する必要がない場合、この属性はオプションです。
    2. Lambda 関数は料理のリストを返し、どの料理を注文したいかをユーザーに尋ねます。つまり、Lambda 関数は OrderFood の Food スロットの Slot を引き出すということです。
  3. ペイロードが OrderFood インテント用である場合:
    1. 前に述べたように、セッション属性が空の場合、Lambda 関数はテスト目的で次​​のセッション属性を作成します。
      {’email’ : ‘user@domain.com’, ‘auth’ : ‘false’, ‘uuid’: None, ‘currentOrder’: None, ‘encryptedPin’: None}
    2. ユーザーが認証されると、Lambda 関数は要求された料理アイテムをセッション属性の currentOrder に追加します。
    3. Lambda 関数はユーザーの電話番号のメールを使って phoneNumbers DynamoDB テーブルをクエリします。
      1. DynamoDB がそのメールアドレスに一致する電話番号を返せない場合、Lambda 関数はそのメールに関連付けられた電話番号を見つけることができなかったことをユーザーに伝え、サポートに連絡するようユーザーに要求します。
    4. Lambda 関数が OTP と uuid を生成します。uuid はセッション属性に保存され、キーと値のペア {uuid :OTP} は onetimepin DynamoDB テーブルにレコードとして保存されます。
    5. Lambda 関数は SNS を使用して OTP をユーザーの電話番号に送信し、OrderFood でピンスロットを引き出すことで受け取った 1 回限りのピンの入力をユーザーに要求します
    6. ユーザーがピンを入力すると、Lambda 関数は sessionAttributes に保存した uuid を持つレコードについて、onetimepin DynamoDB テーブルをクエリします。
      1. ユーザーが間違ったピンを入力した場合、Lambda 関数は新しい OTP を生成し、DynamoDB に保存し、セッション属性で uuid を更新し、この新しい OTP を SNS 経由でユーザーに再度送信し、ユーザーにピンの再入力を求めます。次のスクリーンショットはこのプロセスを表しています。
      2. ピンが正しい場合、Lambda はユーザーが要求している料理を検証します。
      3. 「Food」スロットタイプが null の場合、Lambda は「OrderFood」インテントの「Food」スロットを引き出すことで、ユーザーが興味のあるデータはどれかを尋ねます。
      4. Lambda は要求された料理アイテムを、セッション属性の「currentOrder」に追加します。
  4. ユーザーが認証されると、セッションの有効期限が切れない限り、後に続く追加アイテムを注文するのに認証は必要とされません。

OTP の暗号化と復号化

このセクションでは、OTP をセッション属性として Amazon Lex に送信する前に、クライアント側から OTP を暗号化する方法をご紹介します。Lambda 関数でセッション属性を復号化する方法も解説します。

OTP を暗号化するには、フロントエンドが Amazon Cognito ID プールを使用してセッション属性として OTP を Amazon Lex に送信する前に、KMS を使って暗号化アクションを実行するアクセス許可を持つ認証されていないロールを設定する必要があります。Amazon Cognito ID プールの詳細については、ドキュメントをご参照ください。

Lambda 関数が暗号化されている場合、OTP を受信したら、Lambda は KMS を使用して OTP を復号化し、Dynamo DB テーブルをクエリして OTP が正しいかどうかを確認します。

前提条件については、こちらのドキュメントをご参照ください。

  1. チュートリアル: ID プールの作成

認証されていない ID が有効になっていることを確認します。

  1. KMS キーを作成する
  2. ステップ 1 で作成された認証されていないロールへの KMS キーのアクセスをロックダウンする。
    1. 認証されていない Amazon Cognito ロールが、この KMS を使用して暗号化アクションを実行できるようにします。
    2. Lambda 関数の IAM ロールに KMS の復号化アクションを許可します。
    3. これを説明する主要なポリシーステートメントの例を 2 つ、次に示します。
      {
            "Sid": "Allow use of the key to encrypt",
            "Effect": "Allow",
            "Principal": {
              "AWS": [
                "arn:aws:iam::<your account>:role/<unauthenticated_role>",
              ]
            },
            "Action": [
              "kms:Encrypt",
            ],
            "Resource": "arn:aws:kms:AWS_region:AWS_account_ID:key/key_ID"
          }
      
      {
            "Sid": "Allow use of the key to decrypt",
            "Effect": "Allow",
            "Principal": {
              "AWS": [
                "arn:aws:iam::<your account>:role/<Lambda_functions_IAM_role>",
              ]
            },
            "Action": [
              "kms:Decrypt",
            ],
            "Resource": "arn:aws:kms:AWS_region:AWS_account_ID:key/key_ID"
          }

これで、フロントエンドを使って OTP を暗号化する準備ができました。

  1. フロントエンドクライアントで GetCredentialsForIdentity API を使用し、認証されていない Amazon Cognito ロールの一時的な AWS 認証情報を取得します。これらの一時的な認証情報をフロントエンドが使用して、AWS KMS サービスにアクセスします。
  2. フロントエンドは KMS Encrypt API を使って OTP を暗号化します。
  3. 暗号化された OTPはセッション属性で Amazon Lex に送信されます。
  4. Lambda 関数は KMS Decrypt API を使って、暗号化した OTP を復号化します。
  5. OTP が復号化されると、Lambda 関数は OTP 値を検証します。

まとめ

この投稿では簡単な例を用いて、Amazon Lex ボットで OTP 機能を使用する方法をご紹介しました。この設計では、AWS Lambda を使用してデータの検証とフルフィルメント (データを保存および取得する DynamoDB、SMS メッセージを公開する SNS、OTP を暗号化および復号化する KMS、KMS を利用するための一時的な AWS 認証情報を取得する Amazon Cognito ID プール) を実行しました。

ここで説明した OTP 機能をボットに組み込むのは難しくありません。OTP ピンをフロントエンドから Amazon Lex にインテントのスロット値またはセッション属性値として渡します。次に Lambda 関数を使って検証を送信して実行すると、ボットは OTP を受け入れる準備ができます。


著者について

Kun Qian は、AWS のクラウドサポートエンジニアです。技術的なガイダンスをお客様に提供し、AWS ソリューションのトラブルシューティングや設計に取り組んでいます。