builderscon tokyo 2019にて WebAuthn について話しました

お疲れ様です、ritouです。

今年も話してきました。

builderscon.io

資料を作っている時、こんなにスクショとって何がわかるんだ?スクショでUXを語れんの?と思いながら用意していました。 前半は振り返りをしつつWebAuthnのパラメータなど細けぇ話をダイアログのスクショと絡めて説明しました。 後半は各サービスの実装の特徴的な部分を整理し、自分のサービスで取り入れる場合に気をつけることが意識できるようにまとめました。 その結果、合計100ページ超えになりましたが、完全な時間管理を実践して50分ちょうどぐらいに発表が終わりました。これはすごい。これできると思わなかった。きっちり時間を収めたので質疑応答もできました。

Tweetでまとめていただいたかたもいらっしゃって、感謝でございます。(このTweetにどんどん繋がってます)

WebAuthnの実装、結構ちゃんとしてるじゃんと思っていただいた方もいたようなので、これが誰かのお役に立てると幸いでございます。

(おまけ)大入りタオル、割と大きくて良さそう(語彙)

ではまた。

GitHubの2要素認証がWebAuthnに対応したらしいので触ってみた

どーも、ritouです。

世の中みんながこの話題に夢中かと思います(大げさ)。

github.blog

GitHubは以前から2要素認証の方式としてセキュリティキーをサポートしてきました。2015年ですって。

github.blog

今回、その実装がWebAuthnに変更になったということです。

forest.watch.impress.co.jp

将来的には“Windows Hello”や“Touch ID”などもサポートされるという。

Windows HelloやmacOSChromeなどを用いたTouch IDが利用可能になるのは、将来の話ではありません。もう対応しています。

www.publickey1.jp

この記事はパスワードレスを実現したかのような書きっぷり。

GitHubの発表では、パスワードレスとしての利用のことが書かれています。

In addition, WebAuthn can make it possible to support login using your device as a “single-factor” security key with biometric authentication instead of a password. Although we’re not ready to announce further plans, we’ll continue to pursue ways to make secure authentication as easy as possible for everyone on GitHub.

パスワードレス を匂わせていますが、最初にも書いた通り、今回のは2要素認証のセキュリティキーの扱いをWebAuthnにしたっていうお話です。

説明は以上にして、あとは適当にスクリーンショットを見ていってください。

設定

Settings -> Security からやっていきます。

f:id:ritou:20190822165903p:plain

早速セキュリティキーの設定に進むとパスワード確認画面となります。

f:id:ritou:20190822170100p:plain

あれ?GitHubのパスワード確認にセキュリティキーって使えたっけ?今回この辺りも変更になったのかもしれません。 とりあえず進むと、セキュリティキー追加のフローに入ります。

f:id:ritou:20190822171005p:plain

WebAuthnの登録フローを使ってセキュリティキーを登録する場合、ユーザーに名前をつけさせるのが一般的になっています。 今まで見てきた実装では、処理が完了した時点で名前を入れさせるやり方でしたが、GitHubの場合は先に入力させるようです。

f:id:ritou:20190822170255p:plain

名前を入れて続けるとダイアログが。

f:id:ritou:20190822171730p:plain

f:id:ritou:20190822171806p:plain

YubiKeyとかの方と両方試すと

  • Cross-Platform / PlatformAuthenticator 両方に対応
  • UserVerification : 必須じゃない
  • ResidentKey : 使わない
  • Attestation : 要求してこない

というあたりは雰囲気でわかります。

設定が完了したので、ログアウトして認証に利用します。

f:id:ritou:20190822172840p:plain

f:id:ritou:20190822172855p:plain

f:id:ritou:20190822172914p:plain

Androidでも使えそうじゃん。ということで、モバイル用の画面から設定したいところですが、よくわからん。

Chromeから無理やりPC用のURL使って設定します。

f:id:ritou:20190822173047j:plain

f:id:ritou:20190822173118j:plain

f:id:ritou:20190822173132j:plain

f:id:ritou:20190822173152j:plain

で、ログアウトしてFIrefoxでやり直してみましょう。

f:id:ritou:20190822174032j:plain

f:id:ritou:20190822174046j:plain

f:id:ritou:20190822174101j:plain

f:id:ritou:20190822174117j:plain

できました。

GitHubは今回、WebAuthn対応したおかげで、

  • 今までのFIDO2/FIDO U2F対応のセキュリティキー(Cross-Platform Authenticator)だけではなく、Windows Hello / Android / macOS(w/ Chrome)といったOSが提供する機能(Platform Authenticator)を利用できるようになった
  • ブラウザもChromeのみだった(?)ものからWebAuthn対応のブラウザ全体へとサポート範囲が広がった
  • 今後対応環境が増えても自動的に追従可能

といったメリットを享受できます。

今回、セキュリティキーのフローも試しましたが省略します。

これをみた誰かが、手元にある自慢のTitanを使ったフローでも紹介してくだされば良いのではないかと思います。

Titan使った記事がでたー!!!

medium.com

そういえば、最初の方でパスワード確認のフォームが変わったかな?みたいなことを書きました。

今まではパスワード確認しかなかった部分にセキュリティキーを使うとあるので、押してみたところWebAuthnのフローになりました。

f:id:ritou:20190823034401p:plain

f:id:ritou:20190823034414p:plain

これは結構便利だと思います。

来週のBuildersconの準備をしなくてはいけないので、これぐらいにしておきます。 今回のGitHubの例も紹介する予定です。

builderscon.io

ではまた。

Transactional Authorization - "XYZ"と呼ばれる認可プロトコルとは

f:id:ritou:20190730041251p:plain

おはようございます、ritouです。

今回はTransactional AuthorizationとしてIETFにDraftが提出されている仕様に注目します。

draft-richer-transactional-authz-02 - Transactional Authorization

⚠これはOAuth 2.0の特定の脆弱性を防いだりするために作られたOAuth 2.0拡張ではありません。まだドラフトなので今後も変わる可能性があります⚠

資料

少し前にプロトコルの紹介をした時の動画もあります。

www.youtube.com

最近のIETFのお集まりでのプレゼンテーション資料も公開されています。

https://datatracker.ietf.org/meeting/105/materials/slides-105-oauth-sessa-transactional-authorization-xyz

"XYZ"と名付けられたこのプロトコルについての情報は、以下のサイトにて整理されています。

https://oauth.xyz/

英語が聞こえて読めて自分で調べる気持ちがあるならこれで十分でしょうが、理解を深めるために中身を見ていきましょう。

XYZとは?

XYZは、トランザクションモデルをベースとした認可プロトコルです。OAuth 2.0の拡張ではありません。 まずは処理の流れをざっくり説明します。ここで出てくる用語の意味はOAuth 2.0と一緒です。

  1. トランザクション開始要求 : クライアントはバックチャンネルでクライアント自身の情報、対象ユーザーの情報、アクセスするAPIなどのリソース情報、ユーザーインタラクションの情報、鍵情報などを認可サーバーに送信する
  2. ユーザーインタラクション : ユーザーインタラクションが必要な場合、認可サーバーはリダイレクト先のURLなどをクライアントに返し、クライアントはフロントチャンネルでの処理が行われる
  3. アクセストークンの発行 : ユーザーインタラクションが不要だったり完了した後、認可サーバーはクライアントにアクセストークンを発行する

ここでのポイントは2点です。

  • OAuth 2.0で言う所のGrantTypeに関わらず、上記のトランザクション開始からアクセストークン発行までの流れは共通
  • 必要最小限のフロントチャンネルの利用

この後紹介しますが、AuthZ Code GrantとImplicit Grantの場合はAuthZ Requestを送って...Client Credentials GrantやROPC Grantの時は直接Token Endpointへ...というように最初からフローが分けて考えられているわけではなく、必ず最初にトランザクション開始要求が送られます。 ユーザー認証やアクセス許可が必要となる場合だけブラウザでリダイレクトなどをしてフロントチャンネルを利用します。

また、いわゆるAuthZ Request/ResponseにはRFC6749で定義されているパラメータに加え、OIDC/PKCE/JARM/TokenBindingなど拡張仕様をトッピングしていくとパラメータが山盛りになり、戻ってきた後のClient側の検証も必要です。 XYZではトランザクション要求に含まれた情報に紐づくシンプルなURLを認可サーバー側が生成してクライアントはユーザーを送ります。 ちょっと前にOAuth 2.0で決済を行いたいときなどのScopeの指定方法についてのブログ記事などもありましたが、フロントチャンネルのクエリパラメータよりもバックチャンネルのJSONデータの方が柔軟な表現ができるだろうという感じです。

この辺りを意識して、OAuth 2.0の各種フローをXYZで表現したらどうなるかを見ていきましょう。

OAuth 2.0 vs XYZ

今回は次の3つがXYZでどのように表現されるかを見ていきます。

  • Authorization Code Flow
  • Device Flow
  • Client Credentials Flow
  • Resource Owner's Password Credentials / Assertion Flow

シーケンスをいくつか載せますが、その中で登場人物はOAuthとほぼ一緒です。

  • Resource Owner(RO)
  • Resource Client(RC)
  • Authorization Server(AS)
  • Resource Server(RS)

それでは見ていきましょう。

Authorization Code Flow

OAuth 2.0でAuthorization Code Flowは最も有名な認可フローだと思いますが、これをXYZで表現します。

f:id:ritou:20190729030323p:plain

ちょっと小さくて見えないかもしれませんが、

  • Transaction Request : RCはOAuth 2.0のAuthZ Requestに含まれるようなパラメータをTransaction Requestとして送信
  • Interaction Response : ASはRCに interaction_urlhandle(transaction handle) を返す
  • User Interaction : RCはROを interaction_url に送り、ユーザー認証、アクセス許可を行う
  • Callback : RCに戻る際にはCSRF対策用 stateinteract(interact_handle) の値がついてくる
  • Transaction continue request : RCは state の検証後、 handle(transaction handle), interact_handle をASに送信
  • Access Token : ASはAccess Tokenを返す。ここで handle として返される値を用いてRefreshも可能(Transactionは続いていくのだ...)

という感じです。

そんなにシンプルになってる感じではないですが、OAuth 2.0で肥大してしまったAuthZ Requestがバックチャンネルに閉じ込められたようにも見えます。

Device Flow

次はいわゆるDevice Flowです。

tools.ietf.org

もう少しでRFCになりそうですね。こいつをXYZで表現するとこうなります。

f:id:ritou:20190730023848p:plain

  • Transaction Request : RCは interact.type=device な値を含む Transaction Request を送信
  • Interaction Response : ASはRCに user_code_urluser_codehandle(transaction handle) を返す
  • User Interaction : RCはROに別端末で user_code_url にアクセスさせ、 user_code を入力した後にユーザー認証、アクセス許可を行う
  • Polling request : RCは待ってる間、定期的に handle(transaction handle) をASに送信する
  • Access Token : 別端末でのアクセス許可が完了したら、ASはAccess Tokenを返す。ここで handle として返される値を用いてRefreshも可能

OAuth 2.0の拡張ではDevice AuthZ Endpointが追加されたりしてましたが、XYZなら Transaction Request を受ける Transaction Endpoint がその辺もやってくれるので、とりあえず AuthZ Code Flow相当のやつと共存できる感じになっています。 仕様的にはもう一つ "user_code" を含まない場合もありますが、Device Flowの仕様で言うところの "Remote Phishing"、OAuth 1.0で言うところの "Session Fixation Attack" あたりのリスクがあるのでTransaction Requestにはもう少し情報が追加される気がします。

ちなみにTransaction Request/Response の値とASの処理をもう少し拡張すればCIBA相当の処理も実現できますね。

Client Credentials Flow

いわゆる 2-legged なフローですが、XYZでも表現できます。

f:id:ritou:20190730030810p:plain

この場合は Transaction Request にuser, interact フィールドが含まれず、ASはRCの情報を検証してAccess Tokenを返します。

Resource Owner's Password Credentials / Assertion Flow

いわゆるROPCやある外部IdPからのAssertionを受け取ってAccess Tokenを返す Token Exchange フローについても、XYZで表現できます。

f:id:ritou:20190730031139p:plain

この場合は Transaction Request のuser フィールドにクレデンシャルや外部IdPのAssertionなどを含み、ASはそれを検証してAccess Tokenを返します。

こんな感じで、OAuth 2.0でサポートされているユースケースについてはXYZでサポートされており、エンドポイントの構成なども統一したものになりそうなことがお分りいただけたかと思います。

基本的な仕様

ここまではイメージしやすいようにOAuth 2.0との比較しながら見てきましたが、細かいところの理解するために必要な基本的な仕様を整理します。

エンドポイント

  • Transaction Request/Response
  • Transaction continue Request
  • Polling Request

など色々出てきましたが、AS側は "Transaction Endpoint" がバックチャンネルにてリクエストを処理し、User Interaction が必要な場合のみフロントチャンネルを使った処理が行われます。

ハンドル

XYZで扱われる情報をASが処理した時に、それらの情報と紐付けたhandle の値を返すことでその後の処理に利用できます。

  • Transaction Handle : Transaction Request からAccess Tokenを返すまでの一連の処理に紐付く
  • Client Handle : Client情報に紐付く
  • User : ユーザー情報に紐付く
  • Interaction : 個々の User Interaction に紐付く
  • Resource Handle : アクセス対象となるリソースセットに紐付く
  • Key Handle : 鍵情報に紐付く
  • Access Token Handle : アクセストークンに紐付く

これがイメージできると理解は楽になるでしょう。

リクエス

XYZの中で、Transaction Request が一番複雑になり得ます。 XYZのサイト上でここに含むパラメータを色々いじって試すことができます。 盛り盛りにした場合はこちら。

{
    // Client の情報
    "client": {
        "name": "My Client Display Name",
        "uri": "https://example.net/client"
    },

    // User Interaction に関する指定
    "interact": {
        "type": "redirect",
        "callback": "https://client.example.net/return/123455",
        "state": "LKLTI25DK82FX4T4QFZC"
    },

    // User に関する指定
    "user": {
        "assertion": "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAsCiAibmFtZSI6ICJKYW5lIERvZSIsCiAiZ2l2ZW5fbmFtZSI6ICJKYW5lIiwKICJmYW1pbHlfbmFtZSI6ICJEb2UiLAogImdlbmRlciI6ICJmZW1hbGUiLAogImJpcnRoZGF0ZSI6ICIwMDAwLTEwLTMxIiwKICJlbWFpbCI6ICJqYW5lZG9lQGV4YW1wbGUuY29tIiwKICJwaWN0dXJlIjogImh0dHA6Ly9leGFtcGxlLmNvbS9qYW5lZG9lL21lLmpwZyIKfQ.rHQjEmBqn9Jre0OLykYNnspA10Qql2rvx4FsD00jwlB0Sym4NzpgvPKsDjn_wMkHxcp6CilPcoKrWHcipR2iAjzLvDNAReF97zoJqq880ZD1bwY82JDauCXELVR9O6_B0w3K-E7yM2macAAgNCUwtik6SjoSUZRcf-O5lygIyLENx882p6MtmwaL1hd6qn5RZOQ0TLrOYu0532g9Exxcm-ChymrB4xLykpDj3lUivJt63eEGGN6DH5K6o33TcxkIjNrCD4XB1CKKumZvCedgHHF3IAK4dVEDSUoGlH9z4pP_eWYNXvqQOjGs-rDaQzUHl6cQQWNiDpWOl_lxXjQEvQ",
        "type": "oidc_id_token"
    },

    // アクセス対象のリソースについての指定
    "resources": [
        {
            "actions": [
                "read",
                "write",
                "dolphin"
            ],
            "locations": [
                "https://server.example.net/",
                "https://resource.local/other"
            ],
            "data": [
                "metadata"
            ]
        }
    ],

    // Access Tokenなどにバインドする鍵情報について
    "key": {
        "jwks": {
            "keys": [
                {
                    "kty": "RSA",
                    "e": "AQAB",
                    "kid": "xyz-1",
                    "alg": "RS256",
                    "n": "kOB5rR4Jv0GMeLaY6_It_r3ORwdf8ci_JtffXyaSx8xYJCCNaOKNJn_Oz0YhdHbXTeWO5AoyspDWJbN5w_7bdWDxgpD-y6jnD1u9YhBOCWObNPFvpkTM8LC7SdXGRKx2k8Me2r_GssYlyRpqvpBlY5-ejCywKRBfctRcnhTTGNztbbDBUyDSWmFMVCHe5mXT4cL0BwrZC6S-uu-LAx06aKwQOPwYOGOslK8WPm1yGdkaA1uF_FpS6LS63WYPHi_Ap2B7_8Wbw4ttzbMS_doJvuDagW8A1Ip3fXFAHtRAcKw7rdI4_Xln66hJxFekpdfWdiPQddQ6Y1cK2U3obvUg7w"
                }
            ]
        }
    }
}

これがハンドルを使って最もシンプルにするとこんな感じにもなり得ます。

{
    "client": "VBUEOIQA82PBY2ZDJW7Q",
    "interact": "JMMLJ6393FI7ST9B1SRS",
    "user": "XUT2MFM1XBIKJKSDU8QM",
    "resources": [
        "dolphin-metadata"
    ],
    "key": "7C7C4AZ9KHRS6X63AJAO"
}

実装を想像すると処理の分岐など若干めんどくさい気もしなくはないですが、その辺は気にせずに行きましょう。

レスポンス

いくつかレスポンスが定義されています。興味がある方は仕様を見てみてください。

アクセストーク

Transaction Endpointから返されるAccess TokenがBearer トークンの場合はこんな感じで表現されます。

{
    "access_token": {
        "value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
        "type": "bearer"
    }
}

Sender-Constrained Token にしたい場合はこんな感じです。

{
    "access_token": {
        "value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
        "jwks": {
            "keys": [
                {
                    "kty": "RSA",
                    "e": "AQAB",
                    "kid": "xyz-1",
                    "alg": "RS256",
                    "n": "kOB5rR4Jv0GMeLaY6_It_r3ORwdf8ci_JtffXyaSx8xYJCCNaOKNJn_Oz0YhdHbXTeWO5AoyspDWJbN5w_7bdWDxgpD-y6jnD1u9YhBOCWObNPFvpkTM8LC7SdXGRKx2k8Me2r_GssYlyRpqvpBlY5-ejCywKRBfctRcnhTTGNztbbDBUyDSWmFMVCHe5mXT4cL0BwrZC6S-uu-LAx06aKwQOPwYOGOslK8WPm1yGdkaA1uF_FpS6LS63WYPHi_Ap2B7_8Wbw4ttzbMS_doJvuDagW8A1Ip3fXFAHtRAcKw7rdI4_Xln66hJxFekpdfWdiPQddQ6Y1cK2U3obvUg7w"
                }
            ]
        }
    }
}

Client 認証

OAuth 2.0の場合は静的/動的なClient認証が済んでいる状態を想定しており、動的な登録(Dynamic Registration)を行う場合は処理が1往復追加されます。 XYZではTransaction RequestにClient情報を含むことで動的な登録も処理を増やさずにできますし、静的な登録の仕組みも使えます。 言い換えるとClient情報を細かく制御できる仕組みとも言えそうですが、まぁ、この辺やりすぎると開発者は混乱しそうです。

まとめ

OAuth 2.0のフロートをXYZで表現するとどうなるかを整理し、現状のXYZサイトに書いてある情報を紹介しました。 OAuth 2.0に求められるユースケース毎の要件を整理し、OAuth 1.0の時のようなバックチャンネルのリクエスト中心のプロトコルとして書き直した感じです。

そう言えば決済アプリなどのクレカ関連の処理でお馴染みの3D Secureでも同じようなフローになっています。

3Dセキュア2とは? EMVCoの3Dセキュア

3D Secure 1.0ではXYZのTransaction Request相当のリクエストでカード情報などを送り、ワンタイムパスワードの確認を行うURLにアクセスします。 3D Secure 2.0ではより情報を増やし、リスクベースの判定をしたりします。 Transactionalと言う名前からも、この辺りは共通する部分だったり参考にできる部分はありそうですね。

今後どのような展開を見せるのかはわかりませんが、引き続きウォッチしていきたいと思います。

告知

idcon は今週末開催です。

idcon.connpass.com

ここに来る人なら今回の XYZ についてもすぐに理解できるよな! 興味があったら懇親会でお話ししましょう。

ではまた🍎

GitHub で使われている Facebook の Delegated Account Recovery とは(概要編)

おはようございます、ritou です。

様々なご都合によりGitHubでTwo-factor authenticationってのを設定している方も多いでしょう。

時に人間は、記憶もスマホも財布も一気に無くしてしまいます。

リカバリー方法を複数用意しておくにこしたことはありません。

2019年7月時点の設定画面あたりのスクショはこんな感じです。

f:id:ritou:20190712022802p:plain

今回はこの中のRecovery optionsの一番下、「Recovery tokens」に注目します。

これ何だ?と思ってカーソルを当てると「Account recovery with Facebook is a simple way to recover your account.」とか出てきます。

f:id:ritou:20190712023201p:plain

今回はこの「Facebookでアカウントリカバリー」とは何かというお話です。

GitHub の機能を使ってみる

GitHubのヘルプに全部書いてあります。

これで満足していただけるようであればそれで良いと思いますが、一応書いてある通りやってみます。

リカバリーの設定

GitHubにて「Settings」->「Security」->「Two-factor authentication」->「Recovery tokens」と進みます。

f:id:ritou:20190712023945p:plain

この機能では 「お前のデータにアクセスはしないけど、こっちのサポートチームがお前のIdentityを検証するために使える。」 「まずはfacebook行って来い」 てなことが書いてあります。

Facebookに行ってみると何かの確認画面が出てきますが、いつものアクセス許可への同意画面とは何か少し違います。

f:id:ritou:20190712024354p:plain

「詳しくはこちら」の先も確認しましょう。

f:id:ritou:20190712024501p:plain

「いかがでしたでしょうか?」でまとめてくるブログ記事みたいな雰囲気のタイトルがついたヘルプページですが、基本的にデータはシェアされないことが書いてあります。

オンにすると設定完了です。

f:id:ritou:20190712024624p:plain

Githubに戻ってきました。

リカバリーの実行

GitHubを一旦ログアウトして、Facebookからリカバリーする手順を踏んでみましょう。

今度はFacebook側で「設定」->「セキュリティとログイン」->「外部アカウントのリカバリー」と進みます。

GitHubの設定があることがわかります。

f:id:ritou:20190715041734p:plain

リカバリーしてみます

f:id:ritou:20190715041823p:plain

(ここは人によりそうですが、私は2段階認証でSMSを設定しているせいか、確認が入りました。)

f:id:ritou:20190715042055p:plain

GitHubに遷移して...こんなメッセージが出てきました。

f:id:ritou:20190716024805p:plain

一発でログインさせるのではなく、サポートチームに「Facebookリカバリトークン使った復活をお願いします」と問い合わせてやってもらう流れです。

(おまけ)ちなみにもう一回試そうと思ったらこんなんなりました。

f:id:ritou:20190715042154p:plain

  終
制作・著作
━━━━━
ⓡⓘⓣⓞⓤ

この仕組み、標準化されたものではなくFacebookの独自のものです。Facebook側のドキュメントを見て仕組みを理解しましょう。

Facebookの仕様を理解する

ここですね。

developers.facebook.com

クローズドβということですが、GitHub以外に使ってるとこあるんでしょうか?なさそう?

概要

概要としては

  • パスワードや連絡先を失った場合のリカバリーに利用可能
  • OAuth/OIDCを用いたソーシャルログインとは異なり、ユーザー情報を共有しないシンプルな仕組み
  • メールやSMSへのコード送信よりもセキュア、連絡先変更時のトラブル回避にも使える

とあります。(私の場合、SMSの確認が入ったので若干気になるところもありますがこの辺は設定次第かもしれません。) 用途についても書いてますがとりあえずリカバリーです。

処理の流れ

登場人物は3者です。

  • Account Provider : Delegated Account Recoveryを利用するサービス (GitHub)
  • Recovery Provider : Delegated Account Recoveryを提供するサービス (Facebook)
  • User : GitHub / Facebookの両方にアカウントを持つユーザー

設定手順としては

  1. UserがAccountProviderにて認証済み or 新規登録中
  2. UserはAccountProviderにてサポートされているRecoveryProviderを選択
  3. AccountProviderはRecoveryTokenを生成、Userのブラウザ上でRecoveryProviderに送信 4 RecoveryProviderはUserにRecoveryTokenを紐付けて保存し、AccountProviderに戻る

という4ステップがあり、実行フローも

  1. Userはリカバリーが必要なことをAccountProviderに伝え、AccountProviderは紐づいているRecoveryProviderにUserをリダイレクト
  2. UserはRecoveryProviderにて認証される(リカバリ用途だとわかっているので、追加認証を求められる場合もある)
  3. RecoveryProviderはAccountProviderから受け取って保存していたRecoveryTokenを含むCountersignedTokenを生成し、Userのブラウザ経由でAccountProviderに送る
  4. AccountProviderはCountersignedTokenがRecoveryProviderからのものであることを検証、RecoveryTokenを検証したら内部のデータを複合化してUserをリカバリーします

という4ステップです。 シーケンスについては上記ページに記載してあります。

技術的なポイントとしては

  • 準備
    • OAuth/OIDCのClient登録的なものは必要なのか
  • 設定
    • AccountProvider(GitHub)が作るRecoveryTokenとはどんなものか
    • AccountProvider(GitHub)はRecoveryTokenをどのようにRecoveryProvider(Facebook)に送るのか
    • RecoveryProvider(Facebook)はRecoveryTokenをどのように扱うのか
  • 実行
    • RecoveryProvider(Facebook)が作るCountersignedTokenとはどんなものか
    • RecoveryProvider(Facebook)はどのようにしてCountersignedTokenをAccountProvider(GitHub)に送るのか
    • AccountProvider(GitHub)はCountersignedTokenをどのように検証するのか
  • 妄想
    • OIDCをシンプルにして同じことできないか

といったあたりが気になりますね。 書いてたらとても長くなって諦めたので、次回プロトコル編として公開したいと思います。

ではまた!

OAuth 2.0 / OpenID Connectにおけるstate, nonce, PKCEの限界を意識する

f:id:ritou:20190708035757p:plain

おはようございます、ritouです。ちなみに予約投稿なのでまだ寝てます。

本日のテーマはこちらです。

OAuth 2.0で言うところのClientの視点から、ここに気をつけて実装しましょうという話ではありません。

OAuth 2.0で言うところのServerの視点からみて、Clientにこんな実装されたらたまんねぇなっていうお話です。

最終的には一緒な気もしますが、とりあえず始めます。

state

OAuth DanceにおけるCSRF対策としての state パラメータについて簡単に整理します。

  1. Clientがセッションに一意に紐づく値として生成、管理
  2. ClientがAuthorization Requestに付与
  3. ServerはAuthorization Requestとして受け取った値をAuthorization Responseにそのまま付与
  4. ClientはAuthorization Responseとして受け取った値を検証

しかし、Clientに次のような実装をされると、state の意味が無くなります。

  • セッション問わず常に同じ値を指定する
  • 実は検証してない

これらをServer側で防ごうとしても、限界があります。

  • state の検証まで行うSDKを提供しても使われないかもしれない
  • AuthZ Requestに state の付与を必須にしても、ワンタイムにしろと定義されているわけでもないためキャッシュして弾くような実装にもできない
  • 検証しているかどうか、Webアプリケーションからの利用などはプラットフォームから配信されるネイティブアプリのように審査で完全にチェックできるわけでもない

Client側の実装が正しく行われなくても処理が完結できる作りになっている以上、Serverとしては手が出せません。

nonce

OIDCの nonce パラメータ、ご存知でしょうか?なんか聞いたことある? なんとかPayの前にちょっとだけ話題になってた「Sign-In with Apple」の実装とOpenID Connectの仕様の差異についての記事を目にされた方もいらっしゃるでしょう。

japanese.engadget.com

nonce 扱いが、この件の仕様の差異として出てくるのですが、一旦整理します。

  1. ClientがAuthZ Requestを送る際に生成、管理
  2. ClientがAuthorization Requestに付与
  3. ServerはAuthorization Requestとして受け取った値をAuthorization Response/Access Token Responseに含まれるID Tokenに含む
  4. ClientはAuthorization Response/Access Token Responseに含まれるID Tokenを検証

ID Tokenに含まれているので、Clientがネイティブアプリ-バックエンドサーバーと言う構成になっていたりしても値を引き継いで検証可能です。

当然こちらも、Clientに次のような実装をされると意味が無くなります。

  • 同じ値が指定される
  • 実は検証してない

これらをServer側で防ごうとしても、state と同様に

  • nonce の検証まで行うSDKを提供しても使われないかもしれない
  • AuthZ Requestにnonceパラメータの付与を必須にして値のハッシュ値とかをキャッシュして弾いてやろうと思っても色々悩む
  • 検証しているかどうか(以下略

となります。

個人的に、キャッシュするとかどうとかのあたりはライブラリで実装しても良いかなと思った時期がありましたが、

  • フォーマット自由だしな...ハッシュで保持するしかないか?
  • いつまで保持するの?データ量...たくさんのClientからリクエストが来るServerだったら...

など、得られる効果に対してなかなか悩みどころが多そうな感じです。 と、これを書きながら、ふと思い出しました。 10年以上前の2007年だか2008年に策定されたOpenID Authentication 2.0っていう仕様では、ここで言うAuthZ ResponseにServer側がnonceを払い出す、かつフォーマットの指定まで定義されていました。Final: OpenID Authentication 2.0 - 最終版

openid.response_nonce 値:長さが 255 文字以下の文字列で、この成功した特定の認証応答に固有のものでなければならない (MUST)。ノンスは、サーバの現在時刻で始まらなければならない (MUST)。またノンスには、それぞれの応答を固有のものとする上での必要に応じて、33-126 の範囲に含まれる ASCII 文字 (空白を除く印刷可能な文字) を追加してもよい (MAY)。

その上で、Client側でもちゃんと検証しろと書かれていました。

"openid.response_nonce" について、当該 OP から、これまでに同じ値のアサーションを受け入れたことがない

今回のnonceパラメータもこれぐらい厳密であれば、Server側で管理できなくもない気もしていますが、現状はやはりClient側の実装が正しく行われなくても処理が完結できる作りであることを受け入れざるを得ません。

ちなみにOAuth 2.0/OIDCよくわかってないけどWebAuthnわかる方は、WebAuthnのフローに出てくる challenge パラメータをイメージしていただけると良さそうです。

WebAuthnでもClient/Authenticatorは手元のブラウザ、セキュリティキーですし、challengeの値をキャッシュして重複を弾く実装を確実に行うのは難しそうな印象です(詳しい人の意見求む)。

PKCE

最近のOAuth/OIDCの議論や実装でよく見かけるようになったPKCEについても整理します。

  1. ClientがAuthZ Requestを送る際に code_verifier, code_challenge, code_challenge_method を 生成、管理
  2. ClientがAuthorization Requestに code_challenge, code_challenge_method を付与
  3. ServerはAuthorization Requestとして受け取った値をAuthorization Codeに紐付けておく
  4. ClientはAccess Token Requestに code_verifier を付与
  5. ServerはAuthorization Codeとcode_veirifier を検証

d.hatena.ne.jp

state, nonceと一緒なのは、Clientが生成するってところです。 state, nonceと異なるのは、Serverが検証するし、その検証をしないと処理が完結しないところです。

と言うことで、Clientに次のような実装をされると意味が無くなる点としては。

  • 同じ値が指定される

ぐらいでしょうか。

  • パラメータ生成機能を持ったSDKを提供しても使われないかもしれない
  • AuthZ Requestへのパラメータの付与を必須にして値のハッシュ値とかをキャッシュして弾いてやろうと思っても(略

と言うあたりは残ります。

まとめ

ここまでを一旦まとめると

  • Clientが生成するものをServerは完全には検証できない : 細かなフォーマットがあればなんとかなるかもしれないが...
  • Clientが検証しなくても処理が完結してしまうものに対してServerは手を出せない

と言う当たり前のお話でした。

今回、なぜこれを書いたかと言うと世界平和のためにTwitterしてたら急に思い出しました。

もう6年前の話なので忘れかけていますが、某プラットフォームでOAuth 2.0(RFC6749/6750)に続いてOpenID Connectの仕様の最低限のところを実装し、これで「〜でログイン」させたいというのを内部の然るべきルートで相談したところ、当時大先輩と呼んでいたセキュリティな人に上述の state の話をされました。 セキュリティレビュー、ちゃんと機能してた!(大事)

ID連携をグループ内だけで展開するので Client のチェックができる環境、SDK利用を強制できるなどの場合はまた別だったかもしれません。 しかし、一般開発者向けに提供される仕組みにおいて、全てのClientの実装を継続的にチェックする/できるような状況にならない限りは許容できない→確かにそうですねという感じで、対策として独自の拡張機能を実装することになりました。

そして独自拡張というのは、PKCE で言う所の code_challenge_method=plaincode_verifier(=code_challenge) を Authorization Server が発行してワンタイムになるように管理するものです。

alpha.mixi.co.jp

  • Client が ServerState を要求し、 Authorization Server が Client と紐付けて発行
  • Client は AuthZ Requestに含み、Authorization Serverは ServerState の有効性、紐付けられている Client を検証
  • Authorization Server は Authorization Code と紐付け、AuthZ Response には含まれない
  • ClientはAccess Token Request に Authorization CodeとServer Stateを含む
  • Authorization Server は それらを検証

code_challenge_method=s256 のようにしようと思ったら、それもできます。 今思うと新しいEndpointを増やしたくなかったのでToken Endpointから返してますが、この用途なら新しいエンドポイントで返すべきだと思っています。 そして、ここから先(標準化に向けた取り組み)をせずに野良のまま放置してしまったのも私の怠慢です。

ちなみに相談の時に nonce の話をしたかどうかは忘れましたが、PKCEが出た後だとしても同様の展開になったと思います。 と、言うことで、最後にもう一度まとめます。

  • state, nonce, PKCEがうまく機能しなくなるClient側の実装を意識しよう
  • 完全ではないということでこれらの仕組みがダメだという話ではなく、リスクをどの程度まで軽減させ、受容できるのかを考えるのが大事でしょう
  • 自分の場合はセキュリティ担当からの指摘によってServerStateと言う独自拡張が爆誕した過去の思い出

以上です。今週も頑張りましょう。

ではまた。

ヌーラボアカウントのWebAuthn/FIDO対応をチラ見する

f:id:ritou:20190705105729p:plain

こんにちは、ritouです。

不正ログインが起こらない平和な世界を目指す 開発者です(意識高い)。

ちょっと前に「決定!」というプレスリリースに「決定!お、おぅ...」となったことから全てが始まりました。

その後、βテストというものに参加させていただきました。

そして宣言通り、7月1日にリリースされたようです。

どんな感じか見てみましょう。

新規登録

現状では、新規登録の時はパスワードベースです。

f:id:ritou:20190705010548p:plain

登録するとメールによる確認が行われます。

f:id:ritou:20190705010700p:plain

セキュリティ デバイスの登録は「セキュリティ」->「セキュリティ デバイスの登録」と進みます。

セキュリティ デバイスの登録

f:id:ritou:20190705011254p:plain

ローカル認証を必須とする、いわゆる UserVerification=true が指定されているようです。

f:id:ritou:20190705011741p:plain

f:id:ritou:20190705011906p:plain

この後、自分で名前をつけます。

f:id:ritou:20190705012132p:plain

登録完了ですね。

f:id:ritou:20190705012213p:plain

複数登録できて、削除もできます。

セキュリティ デバイスを登録した時に「セキュリティイベント」と言うところに履歴が残されています。 βテストの時に気付きませんでしたが、クレデンシャルの状態変更と言う意味ではこのイベントもメールにて通知する機能があっても良いかもしれませんね。

ログイン

ログインでは、最初にメアドを入力します。

f:id:ritou:20190705012332p:plain

セキュリティ デバイスの登録があればそれでログインするように分岐します。

f:id:ritou:20190705012452p:plain

ResidentKeyによるユーザーネームレスなフローは使っていないようですが、対応状況やユーザーによって認証方式が異なる場合があるケースでは現状の実装で良さそうですね。

パスワード認証、セキュリティ デバイスを用いた認証のどちらからでも、新しい端末からのログイン時には「ログインアラート」が送られます。

以上がヌーラボアカウントにおける登録・認証フローのWebAuthn/FIDO対応です。

PCの画面を紹介しましたが、Android端末とかからも基本的に同じ感じだと思います。

現在のWebAuthnの対応状況において既存の認証方式への影響を抑えつつ、新しい認証方式を取り入れることをシンプルに表現されていると思います。

パスワードレスに向けて

今後、パスワードレスに意識を向けていくにあたり、わずかに残っているパスワード認証への依存を取り除くことで、WebAuthn/FIDOではなくSMSの扱いなど、複数の認証方式に対応できる設計ができると思います。

  • 新規登録 : パスワード/メアド入力の後にメール確認 -> メアド入力してメール確認した後にパスワードの設定/セキュリティ デバイスの登録
  • リカバリー : 登録済みのメアドにメールを送った後にパスワード再設定 / セキュリティ デバイスの削除、再登録を可能にする

参考 : WebAuthn など新しい認証方式を受け入れる際の「アカウントリカバリー」の考え方 - Qiita

関連機能

ヌーラボアカウントには下記の3つの機能があります。

  • ログインアラート : ログイン成功時に登録されているメールアドレスに通知
  • ログイン履歴 : ログイン方法や日時などの情報が保存されており、閲覧可能
  • セキュリティイベント : セキュリティキーの追加などの情報が保存されており、閲覧可能

これらがあれば、最近よく聞く不正ログインなどにも気付けそうですね。

個人的には、セッション管理&破棄の仕組みが「明示的に」提供されているともう言うことありません。

参考 : ユーザー認証の緊急事態に備えて提供しておきたい、セッション管理とセキュリティイベントログについて - Qiita

パスワード認証ではパスワードリセットのタイミングで既存のセッションを切ったりする設計もそこそこ一般的ですが、WebAuthn/FIDOの場合はどうでしょうかね。 この辺りも認証方式とセッション管理の依存を切り離して、ユーザーが任意のタイミングで管理できる形が良いのかなと思います。

まとめ

ヌーラボアカウントのWebAuthn/FIDO対応を見てみました。

  • 既存のパスワード認証に与える影響を抑えつつ認証方式を追加するシンプルな実装
  • 関連機能も揃っており、ユーザーが安心して使えそう
  • パスワードレスへの移行の第一歩として定番の実装形式と言えそう

以上です。

builderscon tokyo 2019にてUXについての話をする予定なので、ヌーラボアカウントについても紹介したいと思います。

builderscon.io

あ、navigator.credentials.* を呼び出したりするときにJavaScriptからFIDO2 Serverっぽいエンドポイントを叩いてるみたいな細かい話を書くのを忘れてました。 別途まとめます。

ではまた。

CIBAの認証フローを体験するためのWebアプリ(途中経過)

おはようございます、ritouです。

ちょっと前から少しずつ作ってたのやつの途中経過です。

CIBAについてはこの辺りの記事をどうぞ。

ritou.hatenablog.com

ritou.hatenablog.com

今回やりたいこと

CIBAの認証フローをデモできる環境を作りたいなーと思っていました。

  • Webアプリ + プッシュ通知でAuthentication Deviceっぽく振舞わせたい
  • 最初は本当に最小限の実装
  • エンドポイントは開放して触れるようにしておく

ちなみに裏でAuthleteは使ってません。使ったらもっと楽にできそうですね。

Client登録

Client名を指定して Client Credentials を取得できます。

$ curl "https://oidc-ciba-demo.gigalixirapp.com/api/client" -d "name=SAMPLECLIENT"
{"client_id":"01DCCK4B1Z(masked)","client_secret":"01DCCK4B22(masked)","name":"SAMPLECLIENT"}

Authentication Deviceの設定

URL

とりあえずPC/AndroidChromeあたりで動くWebアプリをADとして利用します。

URL : https://oidc-ciba-demo.gigalixirapp.com/

OIDCIBADEMO_URL

ログインとプッシュ通知の設定

Firebase Cloud Messaging を利用します。サポートされていない環境では動きません。

まずログインします。

Screenshot_20190603-022503

Push通知も設定します。

Screenshot_20190603-022547

これで準備完了です!

Screenshot_20190603-022612

ちなみにログアウトしたらメアドはDBから削除されてプッシュ通知も来なくなります。

Authentication Flow

Authentication Request

このリクエストが正しく処理されるとプッシュ通知が飛んでいくはずです。

$ curl -X POST "https://oidc-ciba-demo.gigalixirapp.com/api/backchannel" -H "Authorization: Basic MDFEQ0NLNE(masked)" -d "scope=openid&login_hint=ritou.06@gmail.com"
{"auth_req_id":"01DCCKYE67JK0AGD08XC9E4EQD","expires_in":3600}

OpenID Provider Obtains End-User Consent/Authorization

まずは通知が来ます。

Screenshot_20190603-023221

いわゆる同意画面です。

Screenshot_20190603-023250

完了です。

Screenshot_20190603-023307

Token Request Using CIBA Grant Type

ClientはTokenエンドポイントから各種トークンを取得できるようになりました。

$ curl -X POST "https://oidc-ciba-demo.gigalixirapp.com/api/token" -H "Authorization: Basic MDFEQ0NLNE(masked)" -d "grant_type=urn:openid:params:grant-type:ciba&auth_req_id=01DCCKYE67JK0AGD08XC9E4EQD"
{"access_token":"THISISDUMMYACCESSTOKEN","expires_in":3600,"id_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6InJzMjU2XzIwMTkwNiIsInR5cCI6IkpXVCJ9.eyJhdF9oYXNoIjoiblQ1bjZicEJyZGlPWmFCcDZJcXdrUSIsImF1ZCI6IjAxRENDSzRCMVpOR0NQNEJLOTVKVFQ5WThCIiwiZW1haWwiOiJob2dlQGV4YW1wbGUuY29tIiwiZXhwIjoxNTU5NTAwNDQ2LCJpYXQiOjE1NTk0OTY4NDYsImlzcyI6Imh0dHBzOi8vb2lkYy1jaWJhLWRlbW8uZ2lnYWxpeGlyYXBwLmNvbSIsInN1YiI6ImVvZ24wV2FrOXBMQWN1WkQyYkhwRWciLCJ1cm46b3BlbmlkOnBhcmFtczpqd3Q6Y2xhaW06YXV0aF9yZXFfaWQiOiIwMURDQ0tZRTY3SkswQUdEMDhYQzlFNEVRRCIsInVybjpvcGVuaWQ6cGFyYW1zOmp3dDpjbGFpbTpydF9oYXNoIjoiRXNROFN5cjR1NWhYbUt4SjltbWlIUSJ9.pE_jdSYwkw7FK4QbBnAXXvWSFTsesTLMglAygEuyuYk5Y5HKwrxzoUEwo_d_mZB4U4V6m8mFNIZqcOrP_cdrSwIOZNgH1BkSJucyzS47SgxgfR-X3y4cmPyCAzkHuJdbMao-ev27cbX9xFtlpw5cS4uIk4pHGtKtLJnhoIowQIpd34tXGPjLxuoOo-3P9N0iYZO6RlxL20N_MucbT4NwrZCgQAQSx_QrVUZjEtsi_OtXo-QRiDXAWILSgwxSpatFcLh6ITQlNNn1U03A5aguTnZRITnbJQoqBmaPRvfIC7Y_cc7N5SCnqpHJFBN2qGfOQTquoP0Vu0FT9eGOHAc8_w","refresh_token":"THISISDUMMYREFRESHTOKEN","token_type":"Bearer"}

ID Tokenをデコードするとこんな感じです。

JWT Header and Payload

まとめ

  • メアドを login_hintにして Poll モードで動かすデモです
  • ClientはDynamic Registrationで誰でも使えます
  • 仕様の他のところは暇なときにいじっていきます

BuildersconにCIBAなどに関連したセッションを応募したので、採択されるといいなーと思っております。

builderscon.io

以上。

Software Design 6月号にWebAuthnなど認証についての記事を書きました

こんばんは、ritouです。

タイトルの通り、ちょっと書きました。

gihyo.jp

「WebAuthn」が導く新時代のパスワードレス認証 【前編】FIDOとWebAuthnが変えるもの …… いとうりょう

6月号では前編として、今使われている認証方式とFIDOの違い、WebAuthnの概要という、個人的には2018年のアウトプットのまとめみたいな記事となっています。

ターゲットとしてはidconに来てくれるようなIdentity Professionalではなく初学者向けのような内容だと思います。 私の知り合いの方々は読まれても物足りない感じになるかもしれませんがご了承ください。

(うまくいけば?)7月号で自サービスにWebAuthn/FIDOを導入するためのポイントというこれまた去年のアウトプットをまとめたような記事が後編として載る予定です。

んで、ちょうど技術書典の直前にこの記事を書いていたため、「文章を書くのって大変だな。。。」「ブログとは違うよな」とか色々思いながら過ごしていました。 5/29に iddance やるぞ!と言い出したのもその影響が出ているかもしれません。

idance.connpass.com

申し込み数が100人に迫っていてありがたいです。 後半のWebAuthnネタは初学者には辛いかもと思うぐらいの濃い内容になりそうが、俺得レベルの濃い話を楽しげに話す人の話を聞くのも良いものです。

ちなみに、この執筆のお声がけをいただいたのは、昨年のbuildersconの発表を聞いたことがきっかけらしいです。 builderscon素晴らしいですね。ということで今年もCfPを出しています。

builderscon.io

昨今のAuthenticator/Clientの対応状況を見ると、いよいよサービスがWebAuthn/FIDOの導入を意識できる段階に入ってきたと思います。 UXに注目して具体的な導入イメージを持てるような話ができればと思っています。 当選するかどうかわからないですが、興味がある方は拡散などお願いします。 OAuth/OIDCあたりでもうひとネタぶっ込みたい。。。

ではまた!

Identity Dance School ってのはじめます。初回は5/29で技術書典特集!

こんばんは、ritouです。

来月の話ですが、こんなのをやります。

idance.connpass.com

Digital Identityの勉強会といえば idcon ってのがあります。

idcon.org

最新の技術トレンド、トピックについて最高レベルの情報が集まり、議論される場と言っても過言ではありません。

その分、参加および発表の敷居が高いと思われているのではないか、とも思っています。

そこで、

  • 初学者も気軽に参加できる
  • ちょっと何かやってみた技術者が発表できる

ような勉強会があると良いのかなと思っていました。

そこで今回用意したのが Identity Dance School です。

f:id:ritou:20190419185830p:plain

なぜDanceか?というあたりは、OAuthの一連のリダイレクトフローのことをOAuth Danceと呼ぶ(ある人はFAPI Dance, OIDC Danceなどとも言ってる)あたりから採用しました(tkudos さんとのやりとりから採用)。

内容というか方針はまだあんまり細かく決めていませんが、とりあえず気軽にDigital Identity技術に触れられる機会を用意できればと思っています。

一番最初に紹介した第1回の勉強会では、技術書典でDigital Identity技術に関する本を出したメンバーに声をかけさせていただきました。

技術書典にて、発表者が出した本をきっかけにしてこの分野に新しく興味をもった方もいらっしゃるのではないでしょうか?いないか?ちょっとぐらいいるっしょ?もしいたら、本の著者と交流して、今後もこの分野に興味を持ってもらえればと思います。

発表者、話の内容は初学者のレベルじゃないので、idcon の常連クラスの人たちもポイントポイントで楽しめるんじゃないかと思います。

そんなところで、定員を80名に増やしときましたのでお時間のあるかたは参加をお願いします。

idance.connpass.com

そう言えば、「行動規範」みたいなのはないのかと聞かれましたが、このレベルの集まりでがっつり用意するのもちょっと大変なので、主催者の私が会場提供する所属企業から怒られない程度の「常識的な範囲」でお願いします。

ではまた。

SPAなClientがトークンを安全に扱えるかもしれない拡張仕様「OAuth2.0 DPoP」とは

おはようございます、ritou です。

今日は日頃の情報収集方法の一つである Mike Jones氏のブログ記事に書いてあったドラフト仕様のご紹介です。

self-issued.info

The specification is still an early draft and undergoing active development, but I believe the approach shows a lot of promise and is likely to be adopted by the OAuth working group soon.

まだDraft中のDraftな状態ですが、まいくたんの推し感が出ているのでざっと見ておきましょう。

OAuth2.0 DPoPとは?

tools.ietf.org

This document describes a mechanism for sender-constraining OAuth 2.0 tokens via a proof-of-possession mechanism on the application level. This mechanism allows to detect replay attacks with access and refresh tokens.

Sender Constrained Token については、この辺りから復習しましょう(この前までちょっと内容間違ってたけど修正しました)。

ritou.hatenablog.com

切符はBearer, 国際線の航空券はSender Constrained Tokenみたいな表現のやつです。

f:id:ritou:20190419023842p:plain

OAuth 2.0のAuthorization Code Grantにおいて、

  • Authorization Code, Refresh Token : Sender Constrained Token
  • Access Token : Bearer Token

であるみたいな内容ですが、これはClient Credentialsを安全に管理できるConfidential Clientのお話です。 SPAで動くようなPublic Clientの場合、PCKEを用いてAuthorization CodeをリクエストしたClientに紐付けるぐらいしかできませんでした。(Implicitのことは忘れろ。)

そして、OAuth 2.0におけるproof-of-possession mechanismの2大仕様と言えばこの2つです。

今回のDPoPはこれらを使えないSPAのようなアプリケーションが扱うAccess Token, Refresh Tokenをアプリケーションレベルで "Sender-Constrained Token" にするための仕様です。Confidential ClientもClient認証と組み合わせられるとあります。

細かいところはこれから変わっていくと思いますが、とりあえず重要なところをつまんでいきます。

基本的な流れ

OAuth 2.0 の Authorization Code Grant で Access Token / Refresh Token を発行し、Protected Resource にアクセスするまでの流れを振り返りましょう。

f:id:ritou:20190419001113p:plain

DPoPの対象となるのは、

  • Access Token Request / Response : Client ID, Authorization Code から Access Token / Refresh Token 発行 (Binding)
  • Resource Access w/ Access Token : Access Token を用いたリソースアクセス (Proof)
  • Access Token Refresh Request / Response : Client ID, Refresh Token から Access Token / Refresh Token 発行 (Proof, Binding)

の部分です。Bindingと書いたところで紐付けを行い、Proofと書いたところで紐付けを証明します。

Access Token Request / Response での Binding

Client が Authorization Code を Token Endpoint に送る際、DPoP-Binding ヘッダを指定します。

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Binding: eyJhbGciOiJSU0ExXzUi ...

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
(remainder of JWK omitted for brevity)

この DPoP-Binding ヘッダに指定する値は以下のようなPayloadを含むJWTです。

{
    "typ": "dpop_binding+jwt",
    "alg": "ES512",
}.{
    "jti": "HK2PmfnHKwXP",
    "http_method": "POST",
    "http_uri": "https://server.example.com/token",
    "exp": "...",
    "cnf":{
        "dpop+jwk": {
             "kty" : "EC",
             "kid" : "11",
             "crv" : "P-256",
             "x" : "usWxHK2PmfnHKwXPS54m0kTcGJ90UiglWiGahtagnv8",
             "y" : "3BttVivg+lSreASjpkttcsz+1rb7btKLv8EX4"
        }
    }
}
  • typ : bindingの時は dpop_binding+jwt, proof
  • alg : none, HS系はダメよ
  • http_method : リクエストのHTTPメソッド
  • http_uri : リクエストのHTTP URI
  • exp : 有効期限
  • cnf : 公開鍵の情報を指定する。bindingの時は必須。

Clientは鍵ペアを生成して秘密鍵を保存しつつ、このようなPayloadに秘密鍵を用いた署名したJWT(JWS)を生成して指定します。 Authorization ServerはこのJWTを検証して公開鍵とAccess Token / Refresh Tokenを紐付けます。

そして、レスポンスでは token_type の値が Bearer+DPoP となります(例は記載されていませんがRFC6749の例をいじるとこんな感じになりそう)。

     HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache

     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"Bearer+DPoP",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
       "example_parameter":"example_value"
     }

Authorization ServerがどうやってAccess Tokenに公開鍵を紐付けるかについては、JWT形式にしてそのまま公開鍵情報を含んでしまうもしくはバックエンドで持っておくとか、よしなに実装します。

Proof

Clientは Bearer+DPoPトークンを利用する際に、DPoP-Proof というヘッダを利用します。

Access Token @ Protected Resources

Access Token を用いて Resource Server にアクセスする時はこうなります。

     GET /resource HTTP/1.1
     Host: server.example.com
     Authorization: Bearer mF_9.B5f-4.1JqM
     DPoP-Proof: eyJhbGciOiJSU0ExXzUi ...

そしてヘッダに指定される値のPayloadはこうなります。

{
    "typ": "dpop_proof +jwt",
    "alg": "ES512",
}.{
    "jti": "HK2PmfnHKwXP",
    "http_method": "GET",
    "http_uri": "https://server.example.com/resource",
    "exp": "..."
}

typ の値に注目ですね。 リクエストを受けたResouce ServerはAccess Tokenに紐付けられた公開鍵でDPoP-Proofヘッダに指定されている値の署名を検証します。 Authorization Server と Resource Server が分かれてる時の公開鍵の扱いとかはよしなにやると。

Refresh Token @ Token Endpoint

Refresh TokenからAccess Tokenを要求するところでも DPoP-Proof ヘッダをつけます。

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Proof: eyJhbGciOiJSU0ExXzUi ...

grant_type=refresh_token
&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
...

このレスポンスに含まれる Access Token にはリクエストの Refresh Token に紐付けられた公開鍵が同様に紐付けられます。

公開鍵の表現

Access TokenがJWT形式だった時やToken Introspection API でRS / AS 間で公開鍵を表現する時は、cnf というクレームを利用します。

まとめ

とりあえず、今書いてある内容をざっくりと見ていきました。

  • mTLSとかToken Binding使えないClientにSender Constrained Tokenを発行し、AS/RSが検証できるようにするための仕様
  • 新しいHTTPヘッダを利用
  • 公開鍵暗号方式を利用
    • Bindingの時は公開鍵情報と署名
    • Proofの時は署名
  • 攻撃者が一旦偽AS/RSを立ち上げてClientからリクエストを受け取り、それを正しいAS/RSに向けても、検証して気づける

細かいところは今後変わるかもしれません。 鍵ペアや公開鍵の扱いなどはWebAuthnの登録/認証機能のあたりがイメージできる人だとすんなり理解できるかもしれません。

SPA向けと言いつつ、じゃあ秘密鍵をどこに保存すりゃいいのかとかもうちょっと補足がないとClientが安心して使えないかもしれませんが、PKCEとこれの組み合わせで「だいぶ」安全になるなら結構使える仕様なんじゃないかなとか思ったりしています。

今後に期待しましょう。

あれ、何かの告知を忘れている気がする...次回でいっか。

ではまた!