1. はじめに
ChatBot は、ユーザーとの対話を自動化する技術として、カスタマーサポートや社内問い合わせシステム、パーソナルアシスタントなど、さまざまな用途で利用されている。特に OpenAI の Realtime API を活用することで、より リアルタイムかつ自然な対話 を実現できるようになった。
一方で、ChatBot を運用する際に重要となるのが ユーザー認証 だ。特に スマートデバイスや IoT 端末、組み込み機器 などでは、パスワード入力が難しく、従来の認証方法が使いにくいケースがある。このような課題を解決する手段として、デバイス認可フロー(OAuth 2.0 Device Authorization Grant)が有効です。
前回の記事では Amazon ECS 上で OpenAI Realtime API を活用した ChatBot に Cognito も用いたユーザー認証を実装したが、今回はさらにデバイス認可フローを導入する方法 について確認する。これにより、ユーザーは スマートフォンやPCを使って簡単に認証を行い、ChatBot へのアクセスが可能になる ため、利便性とセキュリティの両立が実現できる。
前回の記事:
2. AWSでデバイス認可フローの実装する例を確かめる
Amazon Cognito と AWS Lambda を使って OAuth 2.0 デバイスフローを実装するのサンプルを用いてデバイス認可フローの実装方法について確認する。
(1) デバイス認可フローの概要
(2) スタックの作成
[ステップ 2: CloudFormation テンプレートを使って実装をデプロイする] の [Launch Stack] からスタックの作成を開始する
(3) スタックのクイック作成
スタックのクイック作成 でパラメータを設定する
パラメータ名 | 説明 |
---|---|
スタック名 | 任意の名称 |
ACertificateARN | ACMで作成した証明書のARN |
ACognitoDomain | Route53でレコードを作成したドメインで任意の名称。親ドメインのIPアドレスが設定されていなければエラーになるので、親ドメインのタイプAに 127.0.0.1 などのダミーを設定しておく |
ACredsEmail | 自分が受け取れる任意の email アドレス |
AFullyQualifiedDomainName (FQDN) | テストサービスを行うドメイン名 |
ResultTokenSet | ID+ACCESS+REFRESH に変更すると ID_TOKEN も取得可能 |
AWS CloudFormationによってIAMリソースが... のチェックマークを入れて スタックの作成 を選択する。
###トラブルシューティング
Amazon Cognito カスタムドメインを作成するには、親ドメインに DNS A レコードが必要である。この設定がないと Cognito のカスタムドメインの作成に失敗する。ダミーで良いので 親ドメインに DNS A レコード を設定すれば解決する。
(4) 設定を完了させる
- CloudFormation コンソール 内の Stacks ページで、作成したスタックをクリック
- 出力タブを開く
- ALBCNAMEForDNSConfiguration を確認する
キー | 値 (一部マスク済み) | 説明 |
---|---|---|
ALBCNAMEForDNSConfiguration | ***--Devic-**********.us-east-1.elb.amazonaws.com |
DNS を ALB のエンドポイントに向けるための CNAME |
DeviceCognitoClientClientID | 78n4qk************* |
認可サーバーとやり取りするためにシミュレートされたデバイスが使用する App クライアント ID |
DeviceCognitoClientClientSecret | 15r0jp1g********************************** |
認可サーバーとやり取りするためにシミュレートされたデバイスが使用する App クライアントシークレット |
TestEndPointForDevice | https://**********/token |
シミュレートされたデバイスがリクエストを行うための HTTPS エンドポイント |
TestEndPointForUser | https://**********/device |
ユーザーがリクエストを行うための HTTPS エンドポイント |
UserPassword | 登録されたメールアドレスに送信 |
テスト用 Cognito ユーザーのパスワード |
UserUserName | *********@**********.com |
テスト用 Cognito ユーザーのユーザー名 |
ALB の DNS を設定
スタックが構築された後、利用する FQDN に対する DNS ゾーン内の DNS CNAME エントリが Application Load Balancer (ALB) の DNS 名を指すようにして設定を完了させる
- ALBCNAMEForDNSConfiguration キーの値をコピー
- Route53でDNS ゾーン内の CNAME DNS エントリをこの値に設定する。Route 53 で DNS ゾーン内に Application Load Balancer を指す CNAME エントリを作成する
設定後に30秒ほど待つと TLS を使って作成したドメイン名でALBにアクセスできる
IDトークン を取得する設定
Lamdaのコンソールで自動作成された関数 DeviceGrant-token を選択する
[設定] タブで環境変数を 編集する
RESULT_TOKEN_SET を追加する。ID+ACCESS+REFRESH を指定することで、アクセストークンだけでなくIDトークンを同時に取得する
環境変数 | 値 |
---|---|
RESULT_TOKEN_SET | ID+ACCESS+REFRESH |
3. デバイス認可フローでトークンを取得する
トークン取得の全体像を確認する。図の黄色の長方形の箇所が curl コマンドや ウェブブラウザ でサーバーにアクセスする箇所になる。
CloudFormation コンソール 内の Stacks ページの [出力] で確認できる次の2つのキー使って確認を進める
キー | 値 (一部マスク済み) |
---|---|
DeviceCognitoClientClientID | 2m5a9b************* |
DeviceCognitoClientClientSecret | 2m5a9b************* |
json形式のアウトプットを読みやすくするために、あらかじめ jq コマンドを用意しておく
brew install jq
トークンの取得
(1) パラメータを設定する
CLIENT_ID=2m5a9b*************
CLIENT_SECRET_ID=2m5a9b*************
ALB=<ALBのDNA名>
(2) デバイスコードとユーザーコードを取得する
CODE=`curl -s -X POST "https://${ALB}/token?client_id=${CLIENT_ID}" \
-H "User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)" \
-H "Authorization: Basic $(echo -n ${CLIENT_ID}:${CLIENT_SECRET_ID} | base64)" | jq`
echo $CODE
DEVICE_CODE=`echo $CODE | jq -r .device_code`
echo $DEVICE_CODE
{
"device_code": "wLGgw9XnE**********",
"user_code": "J2MVP***",
"verification_uri": "https://**********/device",
"verification_uri_complete": "https://**********/device?code=J2MVP***&authorize=true",
"interval": 5,
"expires_in": 1800
}
(3) 認証URLを開き、ユーザーコードを入力してサインインする
- 表示された認証URLを開く
- ユーザーコードを入力
- Amazon Cognito へのログイン
ブラウザで、 verification_uri にアクセスします。
ログインしていない場合にはユーザー名とパスワードでログイン
ここでエラーが表示される場合には今回のサイトに関連するブラウザのCookieをすべて削除してから再度アクセスしてログインし直せば良い・
再度 verification_uri にブラウザでアクセスして user_code を入力し Authorize を選択する
成功すると下記の表示がされる
(4) トークン を取得する
TOKENS=`curl -s -X POST "https://${ALB}/token?client_id=${CLIENT_ID}&device_code=${DEVICE_CODE}&grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-H "User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)" \
-H "Host: ${ALB}" \
-H "Authorization: Basic $(echo -n ${CLIENT_ID}:${CLIENT_SECRET_ID} | base64)" | jq`
echo $TOKENS
{
"id_token": "eyJ***************************",
"access_token": "eyJ***************************",
"refresh_token": "eyJ***************************",
"expires_in": 3600
}
IDトークン、アクセストークンとリフレッシュトークンの取得を確認した。
IDトークン を取得する設定 を忘れると IDトークンは取得できないので注意
(5) id_token と access_token を環境変数に設定しておく
ID_TOKEN=`echo $TOKENS | jq -r .id_token`
ACCESS_TOKEN=`echo $TOKENS | jq -r .access_token`
4. トークンを使ってサービスにアクセスする
テスト用に Hello World アプリ を用意して、そのサービスにトークンを使ってアクセスする
(1) Hello World Lamda 関数 を作成
Lamdaのコンソールで [関数を作成] する
次の設定値で関数を作成する
設定 | 設定値 |
---|---|
関数の作成 | 一から作成 |
関数名 | helloworld |
ランタイム | Python 3.9 |
アーキテクチャ | arm64 |
[関数の作成] を選択し作成する
(2) API Gateway の作成
API Gateway のコンソールからREST APIを作成する
a. オーソライザーの設定
[オーソライザー] -「[オーソライザーの作成] を選択する
次の設定値でオーソライザーを作成する
項目 | 設定値 |
---|---|
オーソライザー名 | 任意の名称 |
オーソライザーのタイプ | Cognito |
Cognito ユーザープール | 自動作成されたユーザープール |
トークンのソース | Authorization |
b. Hello World Lamdaを割り当てる
作成したAPIの [リソース] のページで [メソッド作成] を選択する
次の設定値でメソッドを作成する
項目 | 設定値 |
---|---|
メソッドタイプ | ANY |
統合タイプ | Lambda |
Lambda 関数 | 先ほど作成したLambda関数 |
c. メソッドリクエストの設定
作成したAPIの [リソース] ページで メソッドリクエストの設定 を [編集] する
次の設定値でメソッドリクエストの設定 を行う
項目 | 設定値 |
---|---|
認可 | オーソライザーの設定 で作成したオーソライザー名 |
リクエストバリデーター | クエリ文字列パラメータ、およびヘッダーを検証 |
[保存] ボタンを選択し編集を保存する
d. APIのデプロイ
[APIをデプロイ] ボタンを選択してAPIをデプロイする
ステージ名は prod として デプロイ を選択する
デプロイしたAPIにアクセスするURL (URLを呼び出すのURL) を ステージ の prod のページで確認する
(3) トークンを使ってAPIにアクセスする
先に確認したデプロイしたAPIにアクセスするURLを通してトークンを使って認証を確かめた。IDトークンで認証されるが、アクセストークンでは認証に失敗することを確認した。
トークンなし の場合
認証に失敗する
curl -s -X GET "https://example.com/prod"
{"message":"Unauthorized"}%
アクセストークン の場合
認証に失敗する
curl -s -X GET -H "Authorization: Bearer ${ACCESS_TOKEN}" "https://example.com/prod"
{"message":"Unauthorized"}%
IDトークン の場合
認証に成功して Hello World Lamda関数が呼び出されたことが確認できる
curl -s -X GET -H "Authorization: Bearer ${ID_TOKEN}"
"https://example.com/prod"
{"statusCode": 200, "body": "\"Hello from Lambda!\""}%
参考資料