Amazon Web Services ブログ
Amazon Cognito と AWS Lambda を使って OAuth 2.0 デバイスフローを実装する
本記事は Implement OAuth 2.0 device grant flow by using Amazon Cognito and AWS Lambda | AWS Security Blog を翻訳したものです。
このブログ記事では、Amazon Cognito に OAuth 2.0 デバイス認可フロー (Device Authorization Grant Flow) を AWS Lambda と Amazon DynamoDB を使って実装する方法を学べます。
インターネットに接続されているが、入力機能が制限されていたり、使いやすいブラウザがなかったりするウェラブルデバイス、スマートスピーカ、動画ストリーミングデバイス、スマートホームデバイス、スマートヘルスデバイスなどのデバイスに対してOAuth 2.0 認可フレームワーク (RFC 6749) を実装する際は、OAuth 2.0 デバイス認可 (RFC 8628) の利用を考えるべきです。この認可フローではデバイスのユーザがスマートフォンなどの高度な入力やブラウザ能力がある2台目のデバイスで認可リクエストを検証できます。このフローを使うことで、OpenID Connect コア仕様 で定められている Proof Key for Code Exchange (PKCE) 付きの認可コードフローの制限に対処することができ、以下のような状況を避ける助けになります。:
- エンドユーザにアプリケーション専用のパスワードを決めさせたり、リモコンを使ったスクリーンキーボードを使わせたりする。
- クライアントアプリケーションや近くにいる人に認証情報をさらすことで、エンドユーザのセキュリティ状況を低下させてしまう。
このシナリオの1つの例は、TV に HDMI で接続する動画ストリーミングデバイスで、ユーザ名とパスワードを1文字ずつリモコンで選択しなければいけない時です。この時に同じ部屋に他の人がいると、入力中に文字をさらしてしまうことになります。
実装の概要
OAuth 2.0 デバイス認可 (RFC 8628) は、IETF の標準規格で、IoT デバイスが独自のトランザクションを開始して、認証されたエンドユーザがネイティブのブラウザを使って安全に確認をできるようにするものです。ユーザがトランザクションを認可すると、図 1 のようにバックチャンネル呼び出しを通じて、エンドユーザがデバイスにリクエストしている事を示す OAuth 2.0 アクセストークンを発行します。
ワークフローは以下の流れになります:
- 認証されていないユーザがデバイスにサービスを要求します。
- デバイスはクライアント ID とクライアントシークレットで認証した上で、ランダムなコードのペア (1つはデバイス用、もう1つはユーザ用) を /tokens に要求します。
- 呼び出された Lambda ファンクションはデバイス用のコード、ユーザ用のコード、スコープ、リクエストをしてきたクライアント ID を保管して、認可リクエストの情報を登録します。
- デバイスはユーザ用のコードをユーザに提供します。
- ユーザはクライアントアプリケーションを認可するために、認証がされた Web ページにアクセスして、ユーザ用のコードを入力します。
- 認可コードを要求するために、Amazon Cognito ユーザプールの認可エンドポイント /authorize にユーザはリダイレクトされます。
- ユーザは設定されている Lambda ファンクションが割り当てられているエンドポイント /callback に認可コードを付けて戻されます。
- Lambda ファンクションは登録してある認可リクエストの情報に認可コードを追加します。
- デバイスはデバイス用のコードを使って、定期的に認可リクエストの状況を確認します。認可リクエストが承認されたら、デバイスはデバイス用のコードを使って Lambda 関数からの JSON 形式の Web トークンのセットを受け取ります。
- この実装では、Lambda ファンクションはデバイスのふりをして Cognito ユーザプールの /token エンドポイントに認可リクエストの情報として保存している認可コードを渡して JSON 形式の Web トークンを受け取り、デバイスに返します。
このフローを実現するために、このブログ記事では以下から構成される実装(ソリューション)を提供します。:
- AWS Lambda ファンクションが割り当てられた 3 つのエンドポイント:
- /token エンドポイント。クライアントアプリケーションからのコード生成、認可リクエストの状況確認、JSON 形式の Web トークンの受け取りなどのリクエストに対応します。
- /device エンドポイント。ユーザに認可リクエストを承認・拒否するための UI を提供したり、認可コードを要求させたりします。
- /callback エンドポイント。認可リクエストを承認したか、拒否したユーザに関連付けられた認可コードを受け取ります。
- Amazon Cognito ユーザプール :
- 2 つの Amazon Cognito アプリクライアント。1 つのアプリクライアントはクライアントアプリケーションのためのです。もう 1 つは、/device エンドポイントを守る Elastic Load Balancing の Application Load Balancer のためのものです。
- 1 つの Amazon Cognito ユーザ。テストのためのものです。
- Amazon DynamoDB テーブル。進行している全ての認可リクエストの状態が保存されます。
実装
実装するには 3 つのステップが必要です:
- Application Load Balancer で実現する公開エンドポイント用に公開ドメイン名 (FQDN) を定め、X.509 証明書を紐付けます。
- 提供されている AWS
CloudFormation テンプレートをデプロイします。 - 公開 FQDN が Application Load Balancer の公開エンドポイントを指すように DNS を設定します。
ステップ 1: DNS 名を選択し、SSL 証明書を作成します。
Lambda関数用のエンドポイントを Application Load Balancer の HTTPS/443 リスナーで公開する際は、インターネットで名前解決できなければいけません。
Application Load Balancer を設定
- 所有している DNS ゾーンから FQDN を選択します。
- その FQDN のための X.509 証明書と秘密鍵を以下のどちらかの方法で関連付けます。:
- 証明書と秘密鍵を生成 を AWS Certificate Manager (ACM) で行います。
- あるいは、証明書と秘密鍵をインポート を ACM で行います。
- ACM に証明書ができれば、ACM コンソール内の Certificates ページを開きます。
- 証明書の詳細を表示させるために、用意した証明書の横にある右矢印 (►) をクリックします。
- 証明書の ARN (Amazon Resource Name) をコピーしてテキストファイルなどに保存します。
ステップ 2: CloudFormation テンプレートを使って実装をデプロイする
このソリューション設定するには、CloudFormation テンプレート実装をデプロイする必要があります。
CloudFormation テンプレートをデプロイする前に、この実装の GitHub レポジトリ を確認することもできます。
CloudFormation テンプレートをデプロイ
- ご自身のアカウントに CloudFormation スタックを作成するには以下の Launch Stack ボタンを押します。
Note: スタックはバージニア北部 (us-east-1) リージョンに作成されます。他の AWS リージョンにこの実装をデプロイするには、ソリューションの CloudFormation テンプレート をダウンロードして選択したいリージョンに変更してからデプロイしてください。
- スタックの設定中に以下の情報を入力する必要があります。:
- スタックの名前。
- AWS Certificate Manager に作成あるいはインポートした証明書の ARN。
- 有効なご自身のメールアドレス。Amazon Cognito テストユーザの初期パスワードがこのメールアドレスに送られます。
- 前のステップで選ばれた FQDN。AWS Certificate Manager に作成あるいはインポートした証明書と関連付けたものです。
- スタックの設定をして Next をクリックし、再度 Next をクリックします。Review ページで、このスタックで CloudFormation が AWS Identity and Access Management (IAM) リソースを作成することを承認するチェックボックスにチェックを入れて下さい。
- スタックをデプロイするために Create stack をクリックします。デプロイには数分かかります。ステータスが CREATE_COMPLETE になると、デプロイは完了です。
ステップ 3: 設定を完了させる
スタックができた後は、利用する FQDN に対する DNS ゾーン内の DNS CNAME エントリが Application Load Balancer の DNS 名を指すようにして設定を完了させる必要があります。
DNS CNAME エントリを作成
- CloudFormation コンソール内の Stacks ページで、作成したスタックをクリックします。
- Outputs タブを開きます。
- ALBCNAMEForDNSConfiguration キーの値をコピーします。
- DNS ゾーン内の CNAME DNS エントリをこの値に設定します。Route 53 で DNS ゾーン内に Application Load Balancer を指す CNAME エントリを作成する方法の詳細については、Amazon Route 53 コンソールを使用したレコードの作成 を参照下さい。
- Output タブにある他の値は、この記事の次のセクションで使用します。
Output キー Output 値の用途 DeviceCognitoClientClientID アプリ クライアント ID。デバイスをシュミレートしたものが認可サーバとやりとりするのに使用します。 DeviceCognitoClientClientSecret アプリ クライアント シークレット。デバイスをシュミレートしたものが認可サーバとやりとりするのに使用します。 TestEndPointForDevice HTTPS エンドポイント。デバイスをシュミレートしたものがリクエストするのに使用します。 TestEndPointForUser HTTPS エンドポイント。ユーザがリクエストするのに使用します。 UserPassword Amazon Cognito テストユーザのパスワード。 UserUserName Amazon Cognito テストユーザのユーザ名。
実装を評価
これで実装をデプロイして設定できました。OAuth 2.0 デバイス認可フローを始めることができます。
独自のデバイスのソフトウェアを実装するまで、デバイス呼び出しの全てを curl ライブラリ、Postman クライアント、何かしらの HTTP リクエストライブラリ、クライアントアプリケーションを実装する言語の SDK などを使用することができます。
以下の全ての HTTPS リクエストは、デバイスがプライベート OAuth 2.0 クライアントである前提です。したがって、HTTP ヘッダの Authorization の値にClient ID:Client Secret を Base 64形式でエンコードして入ることになります。
1 つ前のセクションで説明したように、デプロイした CloudFormation スタックの Output の表で、エンドポイントの URI、クライアント ID、クライアント シークレットを知ることができます。
クライアントアプリケーションからフローを開始
このブロク記事で紹介する実装では、どのようにユーザーがデバイスに認可リクエストの開始を依頼するか、どのようにユーザーがリクエストを確認するためにユーザー用のコードとURIを入力するか定めなければいけません。ですが、以下のような HTTPS POST リクエストを送ることでデバイスの振る舞いをエミュレートすることもできます。リクエストの送信先は、Application Load Balancer で保護されている Lambda 関数で実現している /token エンドポイントで、適切な HTTP Authorization ヘッダ を付ける必要があります。ヘッダの値は以下から構成されます。:
- プレフィックスに Basic。Authorization ヘッダのタイプを表します。
- 区切りとして 1 つのスペース
- 以下の文字列を連結して Base64 エンコードしたもの:
- クライント ID
- 区切り文字としてコロン (:)
- クライアント シークレット
以下のような JSON メッセージがクライアントアプリケーションに返ってきます。
クライアントアプリケーションから認可リクエストの状況を確認
Application Load Balancer で保護されている Lambda 関数で実現している /token エンドポイントに対して、以下のような HTTPS POST リクエストを送ることで、クライアント アプリケーションが定期的に認可リクエストの状態を確認する処理をエミュレートすることができます。このリクエストには、前のセクションで説明したのと同様の HTTP Authorization ヘッダーが必要です。
クライアントアプリケーションは認可リクエストが承認されるまでエラーメッセージを受け取ります。認可がされていない場合は authorization_pending が、ポーリングの頻度が高すぎる場合は slow_down が、コードの有効期限が切れてしまった場合は expired といった理由が含まれています。以下の例は、authorization_pending エラーメッセージの場合です。
ユーザ用のコードを入力して認可リクエストを承認
次に、ユーザ用のコードで認可リクエストを承認することができます。ユーザとして操作するには、ブラウザでクライアントアプリケーションが提供している verification_uri を開きます。
もし、Amazon Cognito ユーザプールでのセッションを保持していなければ、まずサインすることが必要になります。
Note: Amazon Cognito のテストユーザの初期パスワードは CloudFormation スタックをデプロイした時に指定したメールアドレスに送られています。
初期パスワードを使ったときは、変更が求められます。新しいパスワードを入力する際は、パスワードポリシーを守る必要があることに気をつけて下さい。認証後、図 8 のような認可ページが表示されます。
クライアントアプリケーションが定めデバイスを通じてユーザに提示したユーザ用コードを入力し、Authorize をクリックします。
操作が成功したら、図 9 に似たメッセージが表示されます。
クライアントアプリケーションからのフローを完了
リクエストが承認された後、Application Load Balancer で保護されている Lambda 関数の /token エンドポイントに以下のような HTTPS POST リクエスト を送ることで、クライアントアプリケーションが承認リクエストのステータスを最終確認する動作をエミュレートすることができます。リクエストには、前のセクションで説明したのと同じ HTTP Authorization ヘッダが必要です。
次のように、JSON 形式の Web トークンがクライアントアプリケーションに返されます。
クライアントアプリケーションはアクセストークンによりユーザの振る舞いに基づきリソースを利用できるようになり、リフレッシュトークンによりアクセストークンを自律的に更新できるようになります。
実装を拡張
実装はデフォルトの構成で提供されます。拡張してセキュリティ機能を追加したり、エンドユーザー体験を最適化したりすることができます。
セキュリティ機能を拡張する
この実装に対して、以下のことが行えます:
- AWS KMS で発行したキーを使用する:
- データベース内のデータを暗号化
- Amazon Lambda ファンクションの設定を保護
- AWS Secret Manager を使用する:
- Cognito アプリケーションクライアントの認証情報のようなセンシティブな情報を安全に保管
- Cognito アプリケーションクライアントの認証情報のローテーションを強制
- データ変更時の整合性を守らせるために、Amazon Lambda のコードを追加実装する
- 攻撃からエンドポイントを保護するために、AWS WAF WebACL を有効にする
エンドユーザ体験をカスタマイズする
以下の表は、利用可能な変数の一部を表しています。
名前 | 機能 | デフォルト値 | 型 |
CODE_EXPIRATION | 生成されるコードの有効期限を指定 | 1800 | 秒 |
DEVICE_CODE_FORMAT | デバイス用コードのフォーマットを指定 | #aA | 使用される文字種を指定 # は数字 a は小文字 A は大文字 ! は特殊文字 |
DEVICE_CODE_LENGTH | デバイス用コードの長さを指定 | 64 | 数値 |
POLLING_INTERVAL | クライアントアプリケーションからポーリングする間隔の最小時間を秒単位で指定 | 5 | 秒 |
USER_CODE_FORMAT | ユーザ用コードのフォーマットを指定 | #B | 使用される文字種を指定: # は数字 a は小文字 b は母音ではない小文字 A は大文字 B は母音ではない大文字 ! は特殊文字 |
USER_CODE_LENGTH | ユーザ用コードの長さを指定 | 8 | 数値 |
RESULT_TOKEN_SET | クライアントアプリケーションに返されるトークンを指定 | ACCESS+REFRESH | ID、ACCESS、REFRESH の文字列を + で区切る |
Lambda 関数内の変数を変更する
- Lambda のコンソール内の Functions ページを開きます。
- DeviceGrant-token 関数をクリックします。
- Configuration タブをクリックします。
- Environment variables タブを選択し、変数の値を変更するために Edit をクリックします。
- デバイスとして新しいコードを生成して、設定した環境変数に基づいて動作が変化している事を確認します。
まとめ
この記事で紹介した例よりビジネスやセキュリティの要件がより複雑な場合も、Amazon Cognito、AWS Lambda、Amazon DynamoDB を使って OAuth 2.0 デバイス認可 (RFC 8628) を独自に実装する手助けになればと思います。エンドユーザは以下の特徴から、モバイルアプリケーションで ID を登録したときと同じレベルのセキュリティと同じ体験の恩恵を得られます。:
- ユーザのモバイルデバイスやコンピュータ上のフル機能のアプリケーションを通じて認証情報が得られる
- 認証情報が認証ソースでのみチェックされる
- 認証体験がエンドユーザの選択した一般的な認証プロセスと一致する
- IoT デバイスの正確なタスク範囲に限定してエンドユーザに委譲された動的な認証情報が、エンドユーザの同意を得て IoT デバイスに提供される
この記事についてフィードバックがあれば、英語版ブログ記事下部にあるコメント欄にコメントして下さい。質問がある場合は Amazon Cognito フォーラム に新しいスレッドで投稿するか、この記事の GitHub レポジトリ にアクセスして下さい。
翻訳は Solutions Architect の辻 義一が担当しました。原文はこちらです。