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 LambdaAmazon 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 アクセストークンを発行します。

Figure 1: The device grant flow implemented in this solution

図 1: 本記事で実装する OAuth 2.0 デバイス認可

ワークフローは以下の流れになります:

  1. 認証されていないユーザがデバイスにサービスを要求します。
  2. デバイスはクライアント ID とクライアントシークレットで認証した上で、ランダムなコードのペア (1つはデバイス用、もう1つはユーザ用) を /tokens に要求します。
  3. 呼び出された Lambda ファンクションはデバイス用のコード、ユーザ用のコード、スコープ、リクエストをしてきたクライアント ID を保管して、認可リクエストの情報を登録します。
  4. デバイスはユーザ用のコードをユーザに提供します。
  5. ユーザはクライアントアプリケーションを認可するために、認証がされた Web ページにアクセスして、ユーザ用のコードを入力します。
  6. 認可コードを要求するために、Amazon Cognito ユーザプールの認可エンドポイント /authorize にユーザはリダイレクトされます。
  7. ユーザは設定されている Lambda ファンクションが割り当てられているエンドポイント /callback に認可コードを付けて戻されます。
  8. Lambda ファンクションは登録してある認可リクエストの情報に認可コードを追加します。
  9. デバイスはデバイス用のコードを使って、定期的に認可リクエストの状況を確認します。認可リクエストが承認されたら、デバイスはデバイス用のコードを使って Lambda 関数からの JSON 形式の Web トークンのセットを受け取ります。
  10. この実装では、Lambda ファンクションはデバイスのふりをして Cognito ユーザプールの /token エンドポイントに認可リクエストの情報として保存している認可コードを渡して JSON 形式の Web トークンを受け取り、デバイスに返します。

このフローを実現するために、このブログ記事では以下から構成される実装(ソリューション)を提供します。:

  • AWS Lambda ファンクションが割り当てられた 3 つのエンドポイント:
    • /token エンドポイント。クライアントアプリケーションからのコード生成、認可リクエストの状況確認、JSON 形式の Web トークンの受け取りなどのリクエストに対応します。
    • /device エンドポイント。ユーザに認可リクエストを承認・拒否するための UI を提供したり、認可コードを要求させたりします。
    • /callback エンドポイント。認可リクエストを承認したか、拒否したユーザに関連付けられた認可コードを受け取ります。
  • Amazon Cognito ユーザプール :
  • Amazon DynamoDB テーブル。進行している全ての認可リクエストの状態が保存されます。

実装

実装するには 3 つのステップが必要です:

  1. Application Load Balancer で実現する公開エンドポイント用に公開ドメイン名 (FQDN) を定め、X.509 証明書を紐付けます。
  2. 提供されている AWS
    CloudFormation
    テンプレートをデプロイします。
  3. 公開 FQDN が Application Load Balancer の公開エンドポイントを指すように DNS を設定します。

ステップ 1: DNS 名を選択し、SSL 証明書を作成します。

Lambda関数用のエンドポイントを Application Load Balancer の HTTPS/443 リスナーで公開する際は、インターネットで名前解決できなければいけません。

Application Load Balancer を設定

  1. 所有している DNS ゾーンから FQDN を選択します。
  2. その FQDN のための X.509 証明書と秘密鍵を以下のどちらかの方法で関連付けます。:
  3. ACM に証明書ができれば、ACM コンソール内の Certificates ページを開きます。
  4. 証明書の詳細を表示させるために、用意した証明書の横にある右矢印 (►) をクリックします。
    Figure 2: Locating the certificate in ACM

    図 2: ACM 内の証明書の位置

  5. 証明書の ARN (Amazon Resource Name) をコピーしてテキストファイルなどに保存します。
    Figure 3: Locating the certificate ARN in ACM

    図 3: ACM 内の証明書の ARN の位置

ステップ 2: CloudFormation テンプレートを使って実装をデプロイする

このソリューション設定するには、CloudFormation テンプレート実装をデプロイする必要があります。

CloudFormation テンプレートをデプロイする前に、この実装の GitHub レポジトリ を確認することもできます。

CloudFormation テンプレートをデプロイ

  1. ご自身のアカウントに CloudFormation スタックを作成するには以下の Launch Stack ボタンを押します。
    Select the Launch Stack button to launch the template

    Note: スタックはバージニア北部 (us-east-1) リージョンに作成されます。他の AWS リージョンにこの実装をデプロイするには、ソリューションの CloudFormation テンプレート をダウンロードして選択したいリージョンに変更してからデプロイしてください。

  2. スタックの設定中に以下の情報を入力する必要があります。:
    • スタックの名前。
    • AWS Certificate Manager に作成あるいはインポートした証明書の ARN。
    • 有効なご自身のメールアドレス。Amazon Cognito テストユーザの初期パスワードがこのメールアドレスに送られます。
    • 前のステップで選ばれた FQDN。AWS Certificate Manager に作成あるいはインポートした証明書と関連付けたものです。

     

    Figure 4: Configure the CloudFormation stack

    図 4: CloudFormation スタックの設定

  3. スタックの設定をして Next をクリックし、再度 Next をクリックします。Review ページで、このスタックで CloudFormation が AWS Identity and Access Management (IAM) リソースを作成することを承認するチェックボックスにチェックを入れて下さい。
    Figure 5: Authorize CloudFormation to create IAM resources

    図 5: CloudFormation が IAM リソースを作成することを承認

  4. スタックをデプロイするために Create stack をクリックします。デプロイには数分かかります。ステータスが CREATE_COMPLETE になると、デプロイは完了です。

ステップ 3: 設定を完了させる

スタックができた後は、利用する FQDN に対する DNS ゾーン内の DNS CNAME エントリが Application Load Balancer の DNS 名を指すようにして設定を完了させる必要があります。

DNS CNAME エントリを作成

  1. CloudFormation コンソール内の Stacks ページで、作成したスタックをクリックします。
    Figure 6: Locating the stack in CloudFormation

    図 6: CloudFormation 内のスタックの位置

  2. Outputs タブを開きます。
  3. ALBCNAMEForDNSConfiguration キーの値をコピーします。
    Figure 7: The ALB CNAME output in CloudFormation

    図 7: CloudFormation の Output 内にある ALB CNAME

  4. DNS ゾーン内の CNAME DNS エントリをこの値に設定します。Route 53 で DNS ゾーン内に Application Load Balancer を指す CNAME エントリを作成する方法の詳細については、Amazon Route 53 コンソールを使用したレコードの作成 を参照下さい。
  5. 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
    • 区切り文字としてコロン (:)
    • クライアント シークレット
    POST /token?client_id=AIDACKCEVSQ6C2EXAMPLE HTTP/1.1
    User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
    Host: <FQDN of the ALB protected Lambda function>
    Accept: */*
    Accept-Encoding: gzip, deflate
    Connection: Keep-Alive
    Authorization: Basic QUlEQUNLQ0VWUwJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY VORy9iUHhSZmlDWUVYQU1QTEVLRVkg
    
    Text

以下のような JSON メッセージがクライアントアプリケーションに返ってきます。

Server: awselb/2.0
Date: Tue, 06 Apr 2021 19:57:31 GMT
Content-Type: application/json
Content-Length: 33
Connection: keep-alive
cache-control: no-store
{
    "device_code": "APKAEIBAERJR2EXAMPLE",
    "user_code": "ANPAJ2UCCR6DPCEXAMPLE",
    "verification_uri": "https://<FQDN of the ALB protected Lambda function>/device",
    "verification_uri_complete":"https://<FQDN of the ALB protected Lambda function>/device?code=ANPAJ2UCCR6DPCEXAMPLE&authorize=true",
    "interval": <Echo of POLLING_INTERVAL environment variable>,
    "expires_in": <Echo of CODE_EXPIRATION environment variable>
}
Text

クライアントアプリケーションから認可リクエストの状況を確認

Application Load Balancer で保護されている Lambda 関数で実現している /token エンドポイントに対して、以下のような HTTPS POST リクエストを送ることで、クライアント アプリケーションが定期的に認可リクエストの状態を確認する処理をエミュレートすることができます。このリクエストには、前のセクションで説明したのと同様の HTTP Authorization ヘッダーが必要です。

POST /token?client_id=AIDACKCEVSQ6C2EXAMPLE&device_code=APKAEIBAERJR2EXAMPLE&grant_type=urn:ietf:params:oauth:grant-type:device_code HTTP/1.1
 User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
 Host: <FQDN of the ALB protected Lambda function>
 Accept: */*
 Accept-Encoding: gzip, deflate
 Connection: Keep-Alive
 Authorization: Basic QUlEQUNLQ0VWUwJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY VORy9iUHhSZmlDWUVYQU1QTEVLRVkg
Text

クライアントアプリケーションは認可リクエストが承認されるまでエラーメッセージを受け取ります。認可がされていない場合は authorization_pending が、ポーリングの頻度が高すぎる場合は slow_down が、コードの有効期限が切れてしまった場合は expired といった理由が含まれています。以下の例は、authorization_pending エラーメッセージの場合です。

HTTP/1.1 400 Bad Request
Server: awselb/2.0
Date: Tue, 06 Apr 2021 20:57:31 GMT
Content-Type: application/json
Content-Length: 33
Connection: keep-alive
cache-control: no-store
{
"error":"authorization_pending"
}
Text

ユーザ用のコードを入力して認可リクエストを承認

次に、ユーザ用のコードで認可リクエストを承認することができます。ユーザとして操作するには、ブラウザでクライアントアプリケーションが提供している verification_uri を開きます。

もし、Amazon Cognito ユーザプールでのセッションを保持していなければ、まずサインすることが必要になります。

Note: Amazon Cognito のテストユーザの初期パスワードは CloudFormation スタックをデプロイした時に指定したメールアドレスに送られています。

初期パスワードを使ったときは、変更が求められます。新しいパスワードを入力する際は、パスワードポリシーを守る必要があることに気をつけて下さい。認証後、図 8 のような認可ページが表示されます。

Figure 8: The user UI for approving or denying the authorization request

図 8: ユーザが認可リクエストを承認あるいは拒否する UI

クライアントアプリケーションが定めデバイスを通じてユーザに提示したユーザ用コードを入力し、Authorize をクリックします。

操作が成功したら、図 9 に似たメッセージが表示されます。

Figure 9: The “Success” message when the authorization request has been approved

図 9: 認可リクエストの承認が完了したら成功メッセージが表示されます。

 

クライアントアプリケーションからのフローを完了

リクエストが承認された後、Application Load Balancer で保護されている Lambda 関数の /token エンドポイントに以下のような HTTPS POST リクエスト を送ることで、クライアントアプリケーションが承認リクエストのステータスを最終確認する動作をエミュレートすることができます。リクエストには、前のセクションで説明したのと同じ HTTP Authorization ヘッダが必要です。

POST /token?client_id=AIDACKCEVSQ6C2EXAMPLE&device_code=APKAEIBAERJR2EXAMPLE&grant_type=urn:ietf:params:oauth:grant-type:device_code HTTP/1.1
 User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
 Host: <FQDN of the ALB protected Lambda function>
 Accept: */*
 Accept-Encoding: gzip, deflate
 Connection: Keep-Alive
 Authorization: Basic QUlEQUNLQ0VWUwJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY VORy9iUHhSZmlDWUVYQU1QTEVLRVkg
Text

次のように、JSON 形式の Web トークンがクライアントアプリケーションに返されます。

HTTP/1.1 200 OK
Server: awselb/2.0
Date: Tue, 06 Apr 2021 21:41:50 GMT
Content-Type: application/json
Content-Length: 3501
Connection: keep-alive
cache-control: no-store
{
"access_token":"eyJrEXAMPLEHEADER2In0.eyJznvbEXAMPLEKEY6IjIcyJ9.eYEs-zaPdEXAMPLESIGCPltw",
"refresh_token":"eyJjdEXAMPLEHEADERifQ. AdBTvHIAPKAEIBAERJR2EXAMPLELq -co.pjEXAMPLESIGpw",
"expires_in":3600
Text

クライアントアプリケーションはアクセストークンによりユーザの振る舞いに基づきリソースを利用できるようになり、リフレッシュトークンによりアクセストークンを自律的に更新できるようになります。

実装を拡張

実装はデフォルトの構成で提供されます。拡張してセキュリティ機能を追加したり、エンドユーザー体験を最適化したりすることができます。

セキュリティ機能を拡張する

この実装に対して、以下のことが行えます:

  • 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 IDACCESSREFRESH の文字列を + で区切る

Lambda 関数内の変数を変更する

  1. Lambda のコンソール内の Functions ページを開きます。
  2. DeviceGrant-token 関数をクリックします。
    Figure 10: AWS Lambda console—Function selection

    図 10: AWS Lambda コンソールで関数を選択

  3. Configuration タブをクリックします。
    Figure 11: AWS Lambda function—Configuration tab

    図 11: AWS Lambda 関数の Configuration タブ

  4. Environment variables タブを選択し、変数の値を変更するために Edit をクリックします。
    Figure 12: AWS Lambda Function—Environment variables tab

    図 12: AWS Lambda 関数の Environment variables タブ

  5. デバイスとして新しいコードを生成して、設定した環境変数に基づいて動作が変化している事を確認します。

まとめ

この記事で紹介した例よりビジネスやセキュリティの要件がより複雑な場合も、Amazon CognitoAWS LambdaAmazon DynamoDB を使って OAuth 2.0 デバイス認可 (RFC 8628) を独自に実装する手助けになればと思います。エンドユーザは以下の特徴から、モバイルアプリケーションで ID を登録したときと同じレベルのセキュリティと同じ体験の恩恵を得られます。:

  • ユーザのモバイルデバイスやコンピュータ上のフル機能のアプリケーションを通じて認証情報が得られる
  • 認証情報が認証ソースでのみチェックされる
  • 認証体験がエンドユーザの選択した一般的な認証プロセスと一致する
  • IoT デバイスの正確なタスク範囲に限定してエンドユーザに委譲された動的な認証情報が、エンドユーザの同意を得て IoT デバイスに提供される

この記事についてフィードバックがあれば、英語版ブログ記事下部にあるコメント欄にコメントして下さい。質問がある場合は Amazon Cognito フォーラム に新しいスレッドで投稿するか、この記事の GitHub レポジトリ にアクセスして下さい。

翻訳は Solutions Architect の辻 義一が担当しました。原文はこちらです。