Webアプリケーションのセッション管理にJWT導入を検討する際の考え方

f:id:ritou:20191126174956p:plain

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

qiita.com

これの初日です。

なんの話か

皆さんは今まで、こんな記事を目にしたことがありませんか?

  • Cookie vs JWT
  • 認証に JWT を利用するのってどうなの?
  • JWT をセッション管理に使うべきではない!

リンク貼るのは省略しますが、年に何度か見かける記事です。

個人的にこの話題の原点は最近 IDaaS(Identity as a Service) として注目を集めている Auth0 が Cookie vs Token とか言う比較記事を書いたことだと思っていますが、今探したところ記事は削除されたのか最近の記事にリダイレクトされてるようなのでもうよくわからん。

なのでそれはおいといて、この話題を扱う記事は

  • クライアントでのセッション管理 : HTTP Cookie vs WebStorage(LocalStorage / SessionStorage)
  • サーバーでのセッション管理 : セッションIDを保持 vs 構造体として保持

あたりの話が混在している気がします。

それぞれの細かい指摘はそれなりに正しそうなんですが、全体として「結局どうなん?」となるでしょう。なりませんか?

この記事は比較対象を細かく整理した上で、"JWTを使うために何を考えるべきか" を紹介します。

まずは、Webアプリケーションにおけるセッション管理方法について振り返ります。

Webアプリケーションにおけるセッション管理方法

Webアプリケーションにおけるセッション管理方法で整理すべきはこの2つの比較です。

それぞれを見ていきます。

Cookie vs Token in WebStorage

Webアプリケーションにおいて

  • Single Page Application によるクライアントサイドからのデータ操作が普及してきたぞ
  • OAuth 2.0 のアクセストークンのようなのを用いて自前/外部のAPIにアクセスするぞ
  • で、アクセストークンってどこに保存すればいいんだっけ?

という流れから、

  • JavaScript から読める LocalStorage に保存するとなると、XSSとかが怖い
  • JavaScript から触らせないように HTTP Cookie で HttpOnly 属性をつけたらセキュア?

というお話になります。

その結果、例えば SPA でのAPIアクセスを考えてる人は

  • SPAからのAPIアクセスに利用する期限の短いアクセストークンはリスク受容して WebStorage でもいいかな?
  • それを生成するために必要なやつは Cookie に保存してそこからアクセストークン払い出そうかな?
  • Cookie は常に送られるしサイズとかあるみたいなのででかいのは入れられないかもな... WebStorage に入れといた値と Cookie の値の組み合わせをうまくできないかな?

みたいになるかもしれません。

この部分を検討するにあたり、保存する値が JWT かどうか、それほど関係ないはずです。

まずはこの特性の違いを意識してどこにどのような値を保存するかを考える必要があるでしょう。

セッションIDを保存 vs エンコードされた構造体を保存

JWT が普及する前から、色々な言語の Web Application Framework では、Cookie を用いたセッション管理の実現方法として次のような方法が実装されてきました。

  • CookieにセッションIDを格納、セッションデータはデータストアに格納する
    • データストアには RDBMemcached などのデータストアを利用する
    • セッションIDの破棄だけではなく、データストアで保存されている値を削除することで "セッションの無効化" が可能
  • エンコードされた構造体をセッションCookieに詰め込む
    • 署名の鍵、暗号化の設定ができる
    • 署名などCookieの文字列の有効性だけを検証に利用する場合、"完全なセッションの無効化は困難"。せめて有効期限の概念が必要。

ちなみに、なんとなくCookie=セッションID、WebStorage=構造体を保存するという前提になってる節もありますが、実際はセッションIDを WebStorage 持つこともできますし、Cookieエンコードした構造体を持つこともできます。

そしてこの二つ、どちらかを選択する前提で語られることも多いですが、両者を組み合わせる方式もあるでしょう。

  • セッションIDを含む構造体をCookie/Tokenに詰め込む
    • 署名をつけられる : データストアから引く前に改ざんなどを検知可能
    • 暗号化できる : 構造体の中身を隠せる
    • セッションID以外の値も含められる : 文字列自体に有効期限などを設定できる
    • データストアがある : 無効化可能、フロントエンドに流通させたくないデータはデータストアに閉じ込めておける

両者の特徴を備えるとなるとそれなりに重い実装にはなるでしょう。 扱うデータがセンシティブであるサービスではこのようなセッション管理方式も比較対象に入れても良いでしょう。

ここまでの話で、各WAFや利用されるライブラリに依存している部分があります。

後者の実装まで考えると結果的に JWTの特徴 と大きく関連する内容にはなりますが、まずはどちらの方法を選択するべきか、あるいは組み合わせるべきかを考える必要があるでしょう。

JWTに親でも●されたかのような JWT as a Session な記事の批判は大体がここまで紹介したあたりの話かと思いますが、ここまでの話は JWT が注目される前から存在するものであり、JavaScript での操作の需要が増えて注目されてはいるものの、「枯れた実装」ならぬ「枯れた設計」でしょう。

要件に合わない場合は使う必要はないわけですが、ここからは "それでもなおJWTの導入を検討したい人" が何を考えるべきかというあたりに触れていきます。

JWTの復習

JWT は次の特徴を持つエンコードフォーマットです。

  • 様々なデータをURL Safeにエンコードできる
  • JWS(RFC7515) で署名をつけたり、JWE(RFC7516) で暗号化したりできる
  • 構造化されたデータをやりとりするための標準的な claim が定義されている(RFC7519)
  • 署名や暗号化のアルゴリズム(RFC7518)、鍵の表現(RFC7517)も豊富

これまで紹介したエンコードされた構造体」を標準化した仕様 として導入を検討すると言う意味になるでしょう。

また、(比較対象についてはおいといて)「HTTP Cookieに比べてまだまだ実績が...」みたいな主張も見かけますが、

  • 様々なプロトコルユースケースで利用されている
  • 対応ライブラリも多い
  • JWT生成、検証方法についても一般的になってる
  • 実装における脆弱性や対策も周知されている

といったあたりは「新しくて危ない仕組み」の時期はもはや過ぎているんじゃないかなと思います。 JWT をセッション管理に利用するために考えるべき点を整理します。

何の値を Payload に入れるか

個人的に属性の扱いと言うあたりでは Cookie と JWT を比較しても良いかなと思っていました。

Webブラウザは、Webサーバーが "Set-Cookie" ヘッダにより指定する属性値によってその後の挙動を決定しますが、それだけで完全に Cookie をハンドリングできるかというとそうでもありません。 例えば、Expires, Max-Ageなどで有効期限を指定してもブラウザの開発者ツールなどでユーザーが変更することもできますし、Webサーバーに送られてきたCookieは値だけなので、指定した通りに扱われていることを検証できません。

それに対し、JWT の claim に属性値を指定することで、HTTPのリクエストを受けた時点でそれが改ざんされていないことを検証できます。 RFC7519 で定義されている claim の値を利用することで、複数パーティ間のやりとりだけではなく、Webサーバーが自分自身でハンドリングする場合の検証にも利用できます。

  • iss : 発行者。セッションを発行するサーバー。
  • aud : 受信者。セッション管理の場合は自分自身など。
  • iat, nbf, exp : 発行日時、有効期限周り

Cookie や WebStorage に保存するトークンに誰が何のために発行したものか、有効期限を検証できるようにしたい場合には JWT の Payload にあるこれらの claim を意識し、Signature の検証と合わせて利用することをお勧めします。

それ以外の claim について、JWT の識別について

  • jti : JWT の識別子として「セッションIDを含む構造体をCookieに詰め込む」場合のセッションIDを指定

が使えます。データストアと組み合わせる際にも使えるでしょう。

ユーザー識別子としては

  • sub : ログインセッションの対象となるユーザーIDを指定

が使えます。

識別子の管理ポリシーに従ってPPIDを入れたりJWEを用いて暗号化しても良いでしょう。

JWT の検証パターンと署名アルゴリズム

Cookie や Bearer Token としてなどの HTTPリクエストにJWTの値が含まれる場合、段階的な検証になるかと思います。

  1. 用途(cty あたりで識別)や署名の検証
  2. jti を用いたセッション検証 : セッションDBの参照(※データストアと組み合わせるなら)

署名アルゴリズムについて、monolithなサービスで発行/検証が同一モジュールで行われるような場合は共通鍵暗号方式でも良いと思います。 サービスの規模が比較的大きくなったりしてこれらの検証処理が発行処理と別のところで行われる(署名検証などはProxyやAPI Gatewayみたいな前の方でやり、必要に応じてアプリケーションでデータストアを引くなど)場合、公開鍵暗号方式に変えて検証用の公開鍵を検証箇所に配布するような設計も必要になるかもしれません。

ちょっと前にJWT の署名検証あたりのノウハウについては Qiita に記事を書いたことがあります。

qiita.com

alg がどうこうな話とかは運用で十分に抑えられるでしょう。

終わりに

今回の記事のネタである Cookie vs JWT みたいな比較記事は年に何度かでてくるものの、"JWTだからこそ●●" という部分にはあまり触れらていないものが多いと感じていました。

2011年頃の仕様策定中から JWT をちょっと離れたところから見守ってきた(?)私としては

eyJちゃん、可愛らしイネ(^o^)😃✋ホント可愛すぎだよ〜😄マッタクモウ😃😍🎵💗

(by おじさん文章ジェネレーター)

という感じではありつつも、あくまで "構造化されたデータを安全に送受信するための標準化された仕様" 以上でも以下でもないですし、特徴を活かせそうなところに使っていけば良いと思います。

また、JWT と関連する仕様として、バイナリデータが利用できる環境においては CBOR Web Token (CWT) あたりの活用も少しずつ広まるのかなと感じています。 乱暴にいうと CWT ってのはエンコード方法としてBase64JSONを使っているところをCBORというエンコード方式を利用するものであり、Payload に送受信したいデータを入れるところや署名検証あたりの設計思想は JWT とほとんど一緒です。 今後はJWTを適用しにくいユースケースにおいて構造化されたデータをやりとりしたい時に CWT が検討されていくでしょう。

今回の記事により、有識者に対する「JWTってどうなんです?」という漠然とした質問、JWTの導入検討の悩みを軽減できることをお祈りしています(突然のお祈り)。

明日(2日目)の 「認証認可技術 Advent Calendar 2019」は...また私です。

ではまた。

iddance 2回目やってきました。

おはようございます。

ritouです。

昨日はこれでした。

idance.connpass.com

資料は公開され次第追加予定です。

人の入り具合

前日当日のキャンセルもいくらかあったものの、みなさん来ていただいてありがたかったです。 懇親会参加者は予想+3人ぐらいの健全運営となりました。

内容

今回の発表者は前回の発表者の一部 + 私でした。

idance.connpass.com

個人的には今回も飛ばしていただいて構わん感じだったのですが、実際は「初学者向け」と言う前提を守るべく空気読んでもらった感じもありました。

私は新規登録について取り上げました。

speakerdeck.com

あまりC向けサービスの登録機能を作る機会なんてないと思いますが、 使い始めのUXは重要ですし認証方式の変化に柔軟に対応していくために基本的なところは整理しておく必要があるだろうと言うことで取り上げました。 APIの部分など時間がなくなってしまったので、また今後取り上げたいと思います。

私のに限らず、感想などのblogやTweetをいただけると発表者も励みになると思いますのでよろしくお願いします。

次回

未定ですが、おそらく来年になるかなーと言うところです。

3回めなので話題を散らしたり、もうちょっと個々の発表の後に質問が出るような会になると良いかなと思っております。

ではまた!

(宣伝)

www.sbbit.jp

www.openid.or.jp

webauthn_study という勉強会で話してきました。

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

昨日はこういう勉強会で話してきました。

web-study.connpass.com

私は WebAuthn をサービスに導入するときに何を考えるべきか、というお題をいただいたのでリカバリーとかリカバリーとかリカバリーの話を中心に細かい仕様よりも認証方式をどう組み合わせるかのような話をしました。

その後ヌーラボの加藤さんのお話を聞けました。

実際に自サービスに導入する前に考えたこと、やったことなどをお話いただきました。 某文章を書くにあたっての疑問をその場で解決しようという流れになり #idcon でおなじみの詳しい人たちが助言をしてくれたおかげで内容も濃いものとなっていたようです。

金曜は #iddance の第2回が行われます。

idance.connpass.com

新規登録はプロビジョニングのないC向けサービスでは重要な機能であり、今後設計を考える機会がありそうなので、現状の整理などをできればと思います。

リカバリーのことを考えないといけないので、ではまた。

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

以上。