idcon29のY!Jの事例からログインフローにおけるWebAuthn対応の課題を考えた

ritouです。

idcon の Yahoo! JAPAN の人の資料と動画を見たメモです。

www.docswell.com

youtu.be

2画面のログインUX

p5

2画面か一回でやるかの話は、個人的にこだわっているスキャンの話もありますね。 Y!JはFederation(RP側)をやっていませんが、マネフォのようにパスワード認証/FederationがあるところにWebAuthnを追加する場合にどうすべきか、みたいなのは別途どこかで整理したい気がしています。

推測

Platform Authenticator だけはなく YubikeyとかのRoaming Authenticatorに対応してる場合、リセットした場合も同じことが起こりそう。 そもそもFIDOはブラウザ - 認証器と多段の処理になっているのでブラウザレイヤーでの完全なコントロールってのも難しそう。 例えば、AndroidChrome -> Firefoxと使っていくとCookieなどではFIDO使った形跡がないけど認証器は使えるみたいな場合があることは知られている。

BackupState

p20

複数登録させないと問題については、この値があるCredentialIdは一つでいいけど、ないやつは複数登録させたいみたいな気持ちにはなりそう。

そういえば、FIDOアライアンスが出してたこの動画にある、AndroidでcaBLE使ってログインした後にWindows側のpasskey作るみたいなUXについて、passkeyがここまでやってくれるみたいなのとこれまでのFIDO認証のプラクティス(Platform Authenticatorのある環境なら登録させちゃう)と干渉してしまう部分はありそうなので、これが更新されてどうなるのかにも注目しています。

www.youtube.com

Conditional UI 万能?

これ便利そうで注目しているものの、なんとなくですが、こいつを認証方式の分岐の主役として使うのは思うよりも難しいのかなーと思ったりします。 非対応のブラウザもあるし、Roaming Authenticatorの扱いは難しい。となるとやはりショートカットやユーザーが他の認証方式を選択した後にWebAuthnに戻ってくる経路として使う感じがいいのかなと思ったりします。

Conditional UIの課題

p39

サービス側で削除しても、認証器側ではまだ有効っぽく振る舞われる問題に関して、ユーザー識別を先にやるパターンでは excludeCredentials が使えるかもしれません。

ベターなUX

p40

この辺、Y!Jのような最短経路を提供しようと思うとなかなか困難なところはあると思います。

  • まずはベタベタなユーザー選択式のUXを用意、とにかく詰まらないように網羅的な経路を用意
  • Cookieとか前回のなんとかで推測がいけない訳ではなく、Conditional UIみたいなのはショートカットとして使っていく
  • 推測失敗やユーザーの選択ミスでこの方式が使えないみたいな残念体験は一定数存在すると想定し、そこからフォールバックの経路をしっかり用意する

ぐらいでどこまで精度を上げていけるかなのかなーと言うところですね。

こう言うのをまとめてpasskey対応とか言うのをヘルプとかで説明するの大変問題 がある。

質問 : クレデンシャル共有

合法、違法問わずパスワードでも公開鍵暗号方式でも共有されている世の中なので使われそうな気はするがサービス側の扱いは変わらなそう。

質問 : 乗っ取りリスク

これはFederationでも通ってきた道だとは思うが、ポリシー判断の際はこれまでのFIDOよりは扱いが下がるかもしれない。 金融機関で使いたい時にプラットフォーム側にAAL2以上を要求、みたいな話は難しそう。

(追記ここから) ↑をちょっと補足すると

(追記ここまで)

他にもあった気がするけど一旦ここまで。

個人的にはもうちょっと Discoverable Credentialsについての細けぇ議論があったら面白いかなと思いました。displayName変えられないとかポツポツあったけど。

ではまた。

フィッシング対策観点でメールでリンクが送られない世界を目指すために我々は何ができるか

ritou です

急に寒くなりましたね。

この記事は何の話か

最近のメール経由のフィッシングの話で、

  • その辺のユーザーにとって、メールの送信元が正規のものかどうかの確認は容易ではない
  • さらに、メール本文に含まれるURLの確認が容易ではない

という現状があります。

前者がうまいこと解決されると一番良いんでしょうけれども、そうもいかないのでしょう? 後者のところでよく「メールに含まれる怪しいリンクをクリックしないでください」と言うのがあります。 そもそも怪しいかどうかがわからないからクリックするわけなので、「怪しい」部分を説明するのも大事なことですが、細けーことは一般ピーポーには無理なので、「メールに含まれるリンクをクリックしないでください」まで振り切る方がいい気がしてます。

しかし、これはこれで

  • サービスはユーザーとのコミュニケーションツールとしてリンクを送る(クリックして欲しい)
  • ユーザーはメールのリンクをクリックせずに、お気に入りなどからサービスにアクセスして内容を確認してください

という不整合が起こり、サービスが期待する処理をユーザーが進めてくれないという事案にもつながりかねません。 こんな状況よりは、

  • サービスがメールを受け取れるユーザーにのみ可能な処理を行わせるためにリンクを送る
  • ユーザーがそのリンクをクリックして処理を続ける

というUXにリスクがある前提で話を進め、

  • メールをコミュニケーションのツールとして捉えるのではなく通知の仕組み程度の用途に止める
  • メールを受けた後にユーザーが適切に処理を継続させられるような誘導ができる世界を目指す

ような変化をしていくという方向性も検討する余地があるのではと思っています。

そこでこんなTweetをしてみました。

我々一般開発者がワーワー声を上げても基本的に何も起こらないもので、セキュリティに詳しい人の集まりでこの辺りが少しでも話題になることがあって何かしらの反応や動きにつながったら良いなと期待しております。

現状に対してこんなことしないといけないのでは?みたいな話はzennの記事で書きました。

zenn.dev

これの続き的な立ち位置で、今回は、 サービスがメールでURLを送ってユーザーがそれをクリックして何かの処理が行われるようなフローをなくすために何が必要なのか をもう少し考えてみます。

そもそもメールでリンクが送られるのはどんな時?

色々あるとは思うんですが、大きく分けて2種類あるかなーと思っております。

  • メールを受け取れるユーザーにだけ次の処理を許したい
    • パスワード再設定リンクを送信
    • マジックリンクでログイン
  • 何かを伝えたい
    • メッセージを受信しました + リンク
    • サービスからのお知らせとか + リンク

他にあったら教えてください。

代替案

メールを受け取れるユーザーにだけ次の処理を許したい : リンククリック -> OTP送信

「メールを受け取ったことを確認したければ、OTPを送ればいいじゃない」 という案です。

OTPを使うと、厳密には「今画面を操作しているユーザーがメールを受け取ったこと」を確認できるのでいわゆる経路外認証としてログインで使われていたりもします。 あと、動線がメール受信後のリンククリックの後にで分かれてしまうのも避けられるので、使い所によってはその後の繋ぎが便利になるかもしれません。 面倒な点といえばOTPのコピペですが、そこはWebOTPで頼む、ということですね。

何かを伝えたい : サービス内コンテンツへの誘導リンク送信 -> お気に入りなどから確認を促す

これもよく言われていることですが、実際は結構辛い気がします。 お気に入りからって言いますけど、お気に入りそんなにちゃんと使われてると思いますか? ってことで、この辺りもう少しいい感じにできないのかなーと前に思ってたことを思い出しました。

まずはブラウザについてですが、例えば Google Chrome とかだとブランクなページを開くと最近使ってたサービスみたいなのを表示してくれたりしますよね。自分ので試したら Gmail / Twitter / GitHub / 自分のブログ ... みたいな感じでした。これも履歴みたいなもんではありますが、このようにざっくりでいいのでサービス単位の管理ができれば良いのでは?と思います。

その上で、サービスは次のような自サービスのメタデータを定義しておき、そこにリンクできる HTTP Header なり meta タグなりを仕込みます。 メタデータの扱いについてはこれまでもいくつかのWeb標準APIなどで利用しているものもあると思いますし、それを束ねる仕組みもできそうです。

あとはブラウザはそのメタデータを読み込みに行ったりキャッシュしたりして、ユーザーを期待するページに誘導可能なのではないか?と言う案です。

  • トップページ
  • お知らせ一覧 : 重要なお知らせがあることをメールで通知しつつここから誘導を試みる
  • ヘルプ / 問い合わせ
  • 利用規約 / プライバシーポリシー : ●●が変わりますメールを送りつつ実態はここから誘導を試みる

画像はイメージです。

なんとなく、こんな感じならお気に入りよりももうちょっとなんとかなるんじゃないか、そうでもないかも、みたいな感じですね。

まとめ

  • フィッシング対策とサービス側の意識の違いなんとかならんか
  • メールからリンクを無くす方法を考えてみた
  • なんだかんだでメールをコミュニケーションツールから通知ツールに格下げしようとしている私をお許しください

以上です。

はてブのコメントどうもです!

サービス登録機能はあるといいなと思うけどiGoogleがまだあれば、ほしかったなと思う気持ちがある

サービス管理のあたりは確かにあの頃欲しかったところですね。

URL入らなくなったらユーザサポートめんどくさくなるなぁという感想。

確かに、ユーザーサポートの負担を上げずに実現できる方法はもうちょっと探っていきたいところです。

今回はメールも通知目的に全振りしてはどうかな話なので、例えば今のSMSで送られる情報ぐらいには最小化できるのでは? という意図で書いています。

ヘルプデスク的な業務もしてる自分としては、FAQのここ見ろやバーンってURLを送りつける機能は残してもらいたい。

これも同意ですが、バーンと送られ的たリンクをクリックしないでください!みたいな現状は切ないので、正規のサービスとの繋ぎの部分はもう少し考えていきたいところです。

FedCM入門 その2 ~ 現状のFedCM実装解説

おはようございます。ritouです。 前回の記事に引き続き、FedCM入門です。

ritou.hatenablog.com

何の話か

6/21(火) 19時から、OpenIDファウンデーション・ジャパン主催のイベントがあります。

openid.connpass.com

明日じゃん。

これに先立って、Federated Credential Management API(FedCM) について 非公式 の入門記事を書いています。 今回は、FedCMの現状の実装について解説します。

それでは始めましょう。

登場人物

FedCMの登場人物は、わりと一般的なID連携のものと同じです。

  • IdP : Identity Provider. 他サービスに対してユーザー情報を提供する サービス
  • RP : Relying Party. IdPのユーザー情報を用いて認証機能を実現する サービス
  • ユーザー : IdP/RPそれぞれを利用するユーザー
  • ブラウザ : FedCMに対応したブラウザ

ID連携フロー

FedCMはGOALとしてOpenID Connect(OIDC)/SAMLを対象にするとか言ってましたが、今のところは RP上にID連携のためのプロンプトを出してOIDCを簡略化したような仕組みでユーザー情報を受け渡してログインさせる というのが実装されています。 言い換えると、現在のFedCMで実装されている機能はGoogleが提供するOne Tap Sign-In and Sign-Up(Twitter, Medium, 他いろんなサービスでGoogleアカウントでログインするか?って出てくるやつ)相当である ということです。

developers.google.com

はじめにFedCMを用いたID連携の流れをざっくり説明します。

  1. ユーザーはIdPにログインしている前提
  2. ユーザーがRPで "IdPでログイン" を利用しようとして、RPはFedCMのAPIを呼び出す
  3. ブラウザはIdPに対してログイン中のアカウント情報(リスト)を要求し、ID連携のためのプロンプトをRPドメイン上で表示する
  4. ブラウザはユーザーが選択したアカウント情報に紐づく認証用トークン(OIDCのIDToken)をIdPに要求し取得したものをRPに渡す。RPはそれを認証機能に利用します。

まず前提として、1. でユーザーがIdPにログイン中である必要があります。一般的なWebアプリケーションのような単一ユーザーがログインできる仕組み、もしくはGoogleのような複数アカウントでログインできる仕組みにも対応しています。

プライバシー保護の観点から、RPが"IdPに誰がログインしているか、誰もログインしていないか" を知りえる状態は良くありません。現状、IdPに誰もログインしていない場合は、RPがFedCMのAPIを呼び出しても汎用的なエラーのみが返されます。この辺、FedCMのユースケースはまた別途考察しようと思っているところですが、実際は ログイン中のユーザーがいる状態のID連携の利便性を上げるショートカット 的な感じに使われることになるのかなーというところです。

2でFedCMのAPIを呼び出す部分はこの後説明します。

3で出てくるプロンプトですが、あるユーザーが単体でログイン状態の場合は次のような画像となります。(私のメアドは公開されてるようなものなのでよし)

「続行」をユーザーがクリックすると、User-Agent は IdP にリクエストが送られ、RPにユーザー情報+認証イベントの情報を含む IDToken (具体的な内容はIdP依存だけど実質OIDC互換)が渡されます。

Googleのように同時に複数アカウントがログインできる仕組みの場合、この画面の前にアカウントリストから選択するプロンプトが入ります。

4ではRPはそれを使ってよしなにログインするなり新規登録するなりをします。ここはRPの要件に依存します。

FedCMではID連携フロー以外にログアウトだったりIDToken無効化(退会?)のリクエストを送ることも可能ですが、現状としてはここをまずおさえておけばいいでしょう。

ID連携のシーケンス

ここまでざっくり説明したFedCMのID連携フローで、実際何が行われるかは公式ドキュメントで公開されています。

developer.chrome.com

シーケンスは以下の通りです。

この図だけで大体わかっちゃう人がいたら是非一緒に働きましょう。

ここからは、公式のドキュメントや自分で作成したRP/IdPの動作例を用いて説明していきます。

0. ブラウザがFedCM対応環境環境かどうかを確認する

実際のID連携のプロダクトでは、FedCMが有効ではない環境のフォールバックも当然必要です。

  • FedCMが対応環境ならば優先的に利用
  • 非対応環境ではこれまで通りのID連携

みたいなところを自然なUXで提供する必要があります。

現状では、RPは以下のコードでFedCMのID連携フローが利用可能かどうかの判定ができます。 FedCMの名前にもなってる FederatedCredential っていう仕組みは既にあるものなので、それとloginが実装されているかを組み合わせで判定します。

if (window.FederatedCredential || FederatedCredential.prototype.login) {
  // If the feature is available, take action
}

ちょっと話はずれますが、個人的に現在の One Tap sign-in and sign-up は未ログイン状態の時にプロンプトを節操なく出してくる印象があるんですが、それと同様ではなくログインや新規登録のフローに入ったときに絞るなどの検討が必要かなという印象です。

1. RPがFedCMの関数を呼び出してID連携を要求

RPがFedCMのID連携を開始する処理は navigator.credentials.get の呼び出しから始まります。 ここでRPはIdPのURLと自分自身の識別子(clientId)を指定します。

const credential = await navigator.credentials.get({
        federated: {
          providers: [{
            url: 'https://ex-fedcm-idp.herokuapp.com/',
            clientId: 'https://ex-fedcm-rp.herokuapp.com/'
          }]
        }
    })

上記サンプル実装では、clientId にRP自身のURLをそのまま実装していますが、これはIdPが提供するClient登録機能などで払い出しされた値を指定することになります。 ちなみに、providers とあるように、ここでは複数のIdPのアカウントとの連携も視野に入れられているように見えますね。 現状の実装としては、最初の1個を読むようになってるような挙動をしていますが、ここで複数が指定されるようになったら一番最初に "プロバイダ選択" みたいなプロンプトになるのかなーという想像をしています。

FedCMではここから FederatedCredential.login() を呼び出すことでRPはブラウザはID連携の処理を開始します。

const nonce = '65a7c572-b4fc-4e27-b899-c67e12ac36a5';
const { id_token } = await credential.login({ nonce });

ここでOIDCにおける nonce パラメータを指定することで、ブラウザがIdPから取得してRPに渡されるIDTokenがこのセッション/リクエストに紐づいていることを検証可能です(いわゆるCSRF/リプレイアタック対策みたいな話)。

ブラウザがIdPに送る最初のリクエストは "Top level domain manifest" と記載されているリクエストです。

GET /.well-known/fedcm.json HTTP/1.1
Host: ex-fedcm-idp.herokuapp.com
Accept: application/json
Sec-FedCM-CSRF: ?1
$ curl "https://ex-fedcm-idp.herokuapp.com/.well-known/fedcm.json"
 -H "Sec-FedCM-CSRF:?1"
 -H "Accept:application/json"
{"provider_urls":["https://ex-fedcm-idp.herokuapp.com/"]}

ここでは、RPが指定したIdPのURLがFedCMに対応しているか、リクエストを送っても良いかどうかを確認します。いわゆるマルチテナントなIdPなどでは provider_urls の値が複数返されることになります。

その後に、ドキュメントで "IdP manifest file" と記載されているエンドポイントにリクエストが送られます。これは IdP で指定されたURLにある "/fedcm.json" というエンドポイントです。

GET /fedcm.json HTTP/1.1
Host: ex-fedcm-idp.herokuapp.com
Accept: application/json
Sec-FedCM-CSRF: ?1
$ curl "https://ex-fedcm-idp.herokuapp.com/fedcm.json"
 -H "Sec-FedCM-CSRF:?1"
 -H "Accept:application/json"
{"accounts_endpoint":"/accounts",
 "branding":{"background_color":"0xFF4500","color":"0xFFFFFF","icons":[{"size":32,"url":"https://ex-fedcm-idp.herokuapp.com/images/icon_32.ico"}]},
 "client_metadata_endpoint":"/client_metadata",
 "id_token_endpoint":"/id_token"}

このレスポンスには各種エンドポイントとプロンプトを出す際のアイコンや色といった情報(branding)が含まれます。

ブラウザはこの後、プロンプトに表示する Client 情報(利用規約、プライバシーポリシーのURL)を要求するために "client_metadata_endpoint" にリクエストを送ります。

GET /client_metadata?client_id=https%3A%2F%2Fex-fedcm-rp.herokuapp.com%2F HTTP/1.1
Host: ex-fedcm-idp.herokuapp.com
Referer: https://ex-fedcm-rp.herokuapp.com/
Accept: application/json
Sec-FedCM-CSRF: ?1
$ curl "https://ex-fedcm-idp.herokuapp.com/client_metadata?client_id=https%3A%2F%2Fex-fedcm-rp.herokuapp.com%2F"
 -H "Sec-FedCM-CSRF:?1"
 -H "Referer:https://ex-fedcm-rp.herokuapp.com/"
 -H "Accept:application/json"
{"privacy_policy_url":"https://ex-fedcm-rp.herokuapp.com/pp",
 "terms_of_service_url":"https://ex-fedcm-rp.herokuapp.com/tos"}

RP じゃなく IdP が RP の metadata を出す というところがやや引っかかりますが、まぁいいでしょう。ここで返されたURLはプロンプト内でリンクとして使われます。

また、このリクエストには当然 client_id の値が含まれますが、この後の Accounts list endpoint へのリクエストには含まれません。 IdPがClientの検証を行うには、この段階で client_id パラメータと Referer ヘッダあたりを利用して検証する必要がありそうです。

次に、ブラウザは Accounts list endpoint に現在ログイン中のアカウントリストを要求します。

GET /accounts_list.php HTTP/1.1
Host: ex-fedcm-idp.herokuapp.com
Accept: application/json
Cookie: ...IdP's Cookie...
Sec-FedCM-CSRF: ?1
$ curl "https://ex-fedcm-idp.herokuapp.com/accounts"
 -H "Sec-FedCM-CSRF:?1"
 -H "Referer:https://ex-fedcm-rp.herokuapp.com/"
 -H "Cookie:..."  -H "Accept:application/json"
{"accounts":
 [
   {"approved_clients":[],
    "email":"ritou.06@gmail.com",
    "email_verified":true,
    "family_name":"Ito",
    "given_name":"Ryo",
    "id":"google_user_114181308725730985237",
    "name":"Ryo Ito",
    "picture":"https://lh3.googleusercontent.com/a-/AOh14GjQ_fcwsIRk6LalbnjCHWzWfk7BkYvX9XAkZP8b8Q=s96-c"}
 ]
}

注目するべきは、ここで1st Party相当のCookieが送られます(6月頭の時点ではSameSite=None(3rd Party相当)のものが送られるようになっているので Issueで連絡済みでしたが今確認したらStrictでもいけました!) RPはFedCMのAPIを利用するだけで、IdPも1st Party相当のCookieのみで利便性の高いID連携を実現できるのがポイントですね。

レスポンスにはログイン中のユーザーリストが含まれ、ユーザー情報としてはユーザー識別子、メールアドレス、名前、プロフィール画像などが返されます。

2. ブラウザがユーザーにIdP/RPのアカウント情報、

概要で説明した通り、ここで複数のアカウント情報が返されたらブラウザはリスト表示、単一の場合は同意のプロンプトを表示します。

3. ブラウザがIdPにIDTokenを要求

ユーザーがブラウザのプロンプト上でID連携することに同意したら、ブラウザはIdPに対象ユーザーのIDTokenを要求します。このリクエストにもIdP向けのCookieが含まれます。

POST /id_token HTTP/1.1
Host: ex-fedcm-idp.herokuapp.com
Referer: https://ex-fedcm-rp.herokuapp.com/
Content-Type: application/x-www-form-urlencoded
Cookie: ...IdP's Cookie...
Sec-FedCM-CSRF: ?1
account_id=google_user_114181308725730985237&client_id=https://ex-fedcm-rp.herokuapp.com/&disclosure_text_shown=true&nonce=1c64ca07-90f8-4eee-b3d4-d0eb871ea816

IdPは対象ユーザーのIDTokenをJSON形式で返します。

{
  "id_token": "eyJ********"
}

4. RPはIDTokenを受け取って認証処理などを行う

RPはブラウザから受け取ったIDTokenを利用して認証処理を行います。 呼び出したときのコードを振り返りましょう。

const nonce = '65a7c572-b4fc-4e27-b899-c67e12ac36a5';
const { idToken } = await credential.login({ nonce });

ドキュメントには { id_token } とありますが現状のChrome Canaryの実装では { idToken } で取得できます。これもそのうち治るでしょう。

この辺りは、RPにとっては、OIDCのImplicit Flowと呼ばれるものと同等の処理です。 細かい違いについては別記事で説明予定ですが、OIDCのIDTokenはJWT形式の文字列であり、次のステップを踏むことで検証できます。

  1. 署名が正しい
  2. iss パラメータがIdPのものである
  3. aud が自身のものである
  4. exp が有効期限内である
  5. nonce が "1. RPがFedCMの関数を呼び出してID連携を要求" で指定したものである

このような検証ステップを終えたら、RPは自サービス内のログインや新規登録処理を行います。

まとめ

今回はFedCMのID連携のフローで送られるリクエスト/レスポンスを説明しました。 ユーザーからの見た目はプロンプトが表示されてそこからID連携が行われるだけですが、裏側でいくつかの処理が行われています。

ここまでの流れはAndroid/PCの新しいChrome Canaryで動作確認できます。

興味があったら試していただいて、気になることがありましたらコメントしてください。

次回予告

現状、FedCMは OIDC寄り の仕様になっているものの、ブラウザ-IdP間のリクエスト/レスポンスはFedCM独自のものです。 特に既存のOIDC IdPの方がこれに対応した実装を行う必要がありますが、そのためにはOIDC/FedCMの仕様間のFit/Gapを整理する必要があります。 例えば、ブラウザのCookieを受け取りつつレスポンスの形式はJSONであるみたいな仕様はOIDCの仕様ではあまり馴染みがないものです。

そこで、次回は

  • RPがID連携を要求する部分、OIDC準拠に近づくにはどうなるべき?
  • OIDC IdPが楽にFedCMに対応するための考え方、違う部分についてはFedCMの仕様がこうなっていると捗る、もしくはOIDCにこのような拡張があると捗る

みたいなところを紹介します。

ではまた!

FedCM入門 その1 ~ ID連携の課題とFedCMのアプローチ

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

何の話か

6/21(火) 19時から、OpenIDファウンデーション・ジャパン主催のイベントがあります。

openid.connpass.com

ここで取り上げられるFedCMについて、3部作ぐらいで 非公式解説 を書いていきます。

  1. ID連携の課題とFedCMのアプローチ 今回はこれ
  2. 現状のFedCM実装解説
  3. FedCM vs OIDC 仕様の差分、RP/IdPの対応、差分解消案

上記イベントでの自分の発表内容は2,3あたりになる予定です。

FedCMとは

FedCMについては、4月末ぐらいにいろいろなドキュメントが公開されました。 英語が読める方はこんな非公式情報を信用せずに、公式なドキュメントを読みましょう。

developer.chrome.com

FedCMで解決したい課題

現在、OpenID ConnectやSAMLを用いたID連携はわりと普及してきましたが、いくつか課題や懸念点があります。 FedCMではID連携で使われているiframe, redirects and cookiesあたりの技術がトラッキング用途のものと区別できないことを問題視しています。

Unfortunately, the mechanisms that identity federation was designed on (iframes, redirects and cookies) can also track users across the web. As the user agent isn't able to differentiate between identity federation and tracking, this makes it difficult to determine when these mechanisms are being used to support identity federation

ID連携の基本動作として、ID連携を利用するサービス(Relying Party, 以下RP)とID連携を提供するサービス(Identity Provider, 以下IdP)の間のリダイレクト、いわゆる「OAuth Dance」があります。 例えば私は Zennに記事を書いております が、Googleアカウントでのログインを始めると一度IdPであるGoogleに遷移してGoogle自体にログインしたり、ログイン中のユーザーからID連携に利用するユーザーを選択したりします。そしてID連携の際にRPに提供される属性情報やリソースアクセスに同意した上でRPに戻ってくるお馴染みのフローのことです。 開発者であれば何が行われているかを割と直感的に理解しやすいかもしれませんが、一般ユーザーはそうではないかもしれません。

このようなID連携フローの利便性を上げるため、いくつかの手法が考えられてきました。 確か2009年とかその辺り...スマホが現在のように普及する前のPC主体の頃、Googleでログインというボタンを押すとポップアップで小さめのGoogleの画面が出てきてログインやユーザー情報の提供への同意が求められ、終わったらそれが閉じて元の画面がログイン状態になるみたいな挙動が実装されました。 なんとなーくおしゃれな感じに立ち上がってくるモーダルの中でログインさせるのではなく、ちゃんとURLを確認可能にする、UIも小さな画面に最適化しつつ現在のログインユーザーやRPに渡される情報などをもれなく表示するといったUX検討が行われ、大手IDプロバイダのSDKなどで実装されるようになりました。

一方で、スマホであればポップアップの挙動が変わるのでリダイレクトでやるしかないかーみたいな話も出てきました。 ID連携だけに止まらないですが、この辺りの利便性を上げる仕組みとして、ブラウザがサポート用のプロンプトを出すような流れが出てきました。 ID連携においてはRPにいる時点でIdPにログイン中のユーザーでログインしますか?みたいなプロンプトを出す仕組みをGoogleAndroidやWeb向けに提供しています。

developers.google.com

私は普段、複数のGoogleアカウントを区別するためにChromeのプロフィールを利用していますが、個人のアカウント以外のところでついうっかり(本当についうっかりです)TwitterのURLを開こうとする とこんな画面になったりします。

これはTwitterドメイン上でGoogleにログイン中のユーザー情報を含むプロンプトを表示し、ワンタップでユーザー情報をやりとりすることで利便性をあげようみたいな仕組みです。よく見ると、他にもmediumとかいろんなサービスで使われていますよ。

先に紹介したポップアップやこのOne Tapみたいな仕組みを実装しようと思うと、3rd Party Cookieとiframeあたりの話を避けては通れません。Googleのサービスであれば開発者はSDKを使うだけなので意識してなかったりしますが、One Tapもブラウザの設定で3rd Party Cookieをブロックすると動かなくなります。

また、ID連携の仕様の中にはRPとIdPの間でセッション同期、ログアウト同期を行うための仕様があります。これらもフロントチャンネルでの処理ではiframe + postMessage を利用しています。

と、ここまで紹介した3rd Party Cookieを利用する仕組みですが、似たような仕組みで広告周りでユーザーの特定や行動把握のために別ドメインとやりとりしたり、リワードのために一瞬Safariを開いてみたいな実装がよく行われてきました。あれもユーザー情報を同期して何かの最適化を行おうとするものですが、ID連携とは別物です。しかし、ブラウザから見た挙動としてはどうなのでしょうか。これらはとても似ていますというかほぼ一緒、区別できません。

昨今、各ブラウザにはプライバシー保護の名の下に3rd Party Cookieを利用する仕組みを減らしたりブラウザから取れる細かい情報を減らしたりする動きがあります。FedCMを含むChromeのプライバシーサンドボックス構想みたいなのもまさにそれです。しかし上述のとおりID連携の仕組みで3rd Party Cookieに依存するものがあるので、3rd Party Cookieが廃止された世界ではそれらは当然動かなくなります。これはなんとかしないといけませんってなるわけですね。

そこでFedCMですよという話です。

FedCMのアプローチ

FedCMの目的は「3rd Party Cookieを廃止してもID連携の仕組みが動く世の中にしよう。必要に応じてブラウザが仲介することでプライバシー面のリスク低減を目指そう」あたりにあります。

The Federated Credential Management API (FedCM) provides a use case specific abstraction for federated identity flows on the web. This purpose-built API allows the browser to understand the context in which the RP and IdP exchange information, inform the user as to the information and privilege levels being shared and prevent unintended abuse.

具体的には次回以降の記事で書く予定ですがざっくりと

  • ID連携にもブラウザが仲介するぞ!
  • RPはブラウザのAPIを呼び出してID連携を要求
  • ブラウザはIdPに対して1st Party Cookie相当のリクエストを送る
  • ブラウザはユーザーのアクション(ユーザー選択や利用の同意)に基づいてIdPから取得した情報をRPへ提供
  • ログアウトやID連携の無効化などもブラウザ経由でRPからIdPに伝えられるようにする

あたりがFedCMの現在のスコープとなっています。

ブラウザが仲介して1st Party Cookieとしてリクエストを行う部分については上記3rd Party Cookie廃止の流れへの対応そのものですが、IdPとしても他のドメインからセッションCookieを参照できなくできることはセキュアになるために喜ばしいことでしょう。 ユーザーアクションに基づいてRPに情報開示が行われる部分は既存とそれほど変わらない気はしますが、その辺り配慮してますと言うのはなんとなく伝わってきます。

実装面で言うと、WebAuthnやWebOTPも同様なのですが、ブラウザがプロンプトなりを出してくれる仕組みの場合はJavaScriptなどでしっかりSDKを作り込まなくても比較的容易にRPが実装可能(もちろんそれをラップしてSDKを提供するのはあり)と言うのが大きいでしょう。

現状のFedCMとChromeの実装状況について、ID連携と書いた部分については上で紹介した One Tap 相当の最低限のログインに必要なユーザー情報のやり取りが実現できるところまで実装されています。PC/Android版のChrome Canaryで動作しますので、次回の記事で紹介します。 正直、ここからOIDC/SAMLがフル互換で動く状態になるまでは時間はかかりそうですが、その辺りの考察も今後記事にする予定です。

次回予告

FedCMの現状の動作と裏でどのような処理が行われているかを説明します。

21日までに3つ記事出せるのだろうか???

ではまた!

FIDOの最新動向から考える巨大プラットフォーマーとの関係

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

Google I/O の "A path to a world without passwords" っていう発表、ご覧になりましたか?

www.youtube.com

FIDO, WebOTP, FedCM...いいネタ揃ってますね!今回はFIDOの話をしましょう。 パスワード認証はもう終わり!時代はパスワードレス認証ですよと言い続けてはや数年経ちましたが、最近こんなプレスリリースが出ていました。

prtimes.jp

すべての人にとってウェブをより安全で使いやすいものにするための共同の取り組みとして、AppleGoogleMicrosoftは本日、FIDOアライアンスとWorld Wide Web Consortium(以下、W3C)が策定した共通のパスワードレス認証のサポートを拡大する計画を発表しました。

何やら大ごと感ありますが、3行でまとめると

  • FIDOは指紋認証などのローカル認証と公開鍵暗号方式を組み合わせた仕組みである
  • FIDOの最大の特徴は それぞれの認証器(セキュリティキーもしくはPCなどの端末にしか存在しない秘密鍵 を利用することであるが、それ故にロスト時のリカバリーや新規端末利用(機種変更など)時に全ての利用サービスで再登録が必要ってところに課題があった
  • Apple, Google, MSなどがプラットフォーマーの強みを活かしてその辺うまく同期する仕組み作ったら課題も解決して便利になるな!ついでにだけど、パスワードに対してパスキーって呼ぶぞ!

といったところです。

1番目はおいておいて、2番目はこれまで段階的にホワイトペーパーなどが出ていた部分ですね。別記事で取り上げました。 今回は3番目を強く全面に押し出してパスワードレスやっていき!みたいなところを印象付けてきているように見えます。それ実現できたらすごいことになるよな!書かれている通り理想を実現できればな!そんなに上手く行かんだろうけどな! っと言う部分を含め、今回はApple, Google, MSといった巨大プラットフォーマーとFIDOの関係について整理します。

これまでのFIDOと巨大プラットフォーマーの関係

FIDOというとYubicoなどのセキュリティキーが印象的ではありますが、実際は端末自体を認証器として利用できる Platform Authenticator が特にコンシューマ分野においては重要だろうと言われてきました。Platform Authenticator側、つまりPC/Mobile端末のFIDO対応みたいなところで考えると、当然Apple/Google/MSの存在を感じずにはいられないですね。**

さらにWebアプリからFIDOを利用するためのWebAuthnについて、肝となるブラウザの対応においてもSafari=Apple, Chrome=Google, Edge=MSとあとはFirefoxなどが割と頑張って対応を進めたことである程度どこでも使える状況になったと言えますがこれはまぁいいか。

一方で、これまで数年にわたりパスワードレス認証やID連携についての動向を共有したりしてきた経験から、FIDOではユーザーが単独でサイトごとに強度の高い認証方式を利用できることで、認証器さえ持っていたらこれまで巨大プラットフォーマーとのID連携を利用してリスクベースなど含めた恩恵を受けてきたところから脱却できるのでは? という声も聞いたことがあります。OpenIDなんとかな立場でID連携いいですよ〜って推していると「認証の全てを巨大プラットフォーマーに握られるのもなぁ」みたいなことを言う人がいて、FIDOならそこから脱却できるのでは?と考えてた人がいました。言いたいこともわかります。

とはいえ、全体としては なんだかんだで巨大プラットフォーマーの対応に支えられてここまで普及が進んでいる と言える気がしていたところに、今回の発表によりそれがさらに加速するのではないかなーと考えています。

プラットフォーマーの力でどこまで便利になるのか?

上述の通り、FIDOにはセキュリティキーや対応端末そのもののロストなどのリカバリー、新規端末からの登録し直し面倒問題という課題もあるわけですが、今まではそれに対応しようとすると 複数の認証器でFIDOが使えるように設定しておく みたいなところしかなく、これも上に書きましたがデバイス自身が認証器として使える端末を複数台持っているようなユーザーでない限りC向けサービスのユーザーにとっては敷居が高そうだな遠いう状態でした。

それを、プラットフォーマーの力技でサポートしていこうというのが今回の話で、この記事が割と明るいです。

www.publickey1.jp

実は各社細かい違いがあったり、得意な部分みたいなのもあるのでざっくり整理すると次のような感じを目指そう!という段階です。

  • Apple / Google / MSアカウント毎にFIDOの秘密鍵(以下、パスキー)を管理できてログイン中の端末同士では セキュアな方法で 共有でき、バックアップもとれる
  • 初めて利用する端末では Bluetoothを利用して手元のスマホから利用可能

前者は少し前に Apple が発表したものそのものです。MSも同様な仕組みをやりたそうな発表をしていました。後者はGoogleAndroid端末で進めているものですね。

zenn.dev

MSは現状のAppleのようにアカウントベースに鍵の共有ができるようになりそうな雰囲気、AppleはいわゆるApple信者的な人であれば前者のみでもOKな感じなのであまり後者の仕組みはやってこない気もしなくはない、GoogleAndroidを用いて他社のプラットフォーム上での存在感を出したいと思っているように見えつつ、一度それをやったら鍵が共有されるような機能を乗せてくるのかなというところです。

blog.google

っていう細けぇ話はありますが、巨大プラットフォーマーの対応により、それぞれのアカウントベースでの鍵管理に近づいていくように見えますね。 今後の課題はプラットフォームを跨いだ鍵管理がどのように行われるかというあたりでしょう。Android -> iPhone に変更、その逆の場合にちゃんと鍵が共有されるような世界になるかどうか、各プラットフォームの利益目的だけでは進まなそうな後一歩を踏み出せるかが重要です。

ID連携 vs FIDO(改)

FIDOが鍵管理の部分でプラットフォーマー支えられる時代が来そうとなると、ID連携との違いがまた気になってきます。

  • ID連携ではサービスはID連携の仕組みだけが必要、とはいえOIDCの接続は必要。認証はIdPで行われもちろんIdPへの認証でFIDOを使うこともできる。
  • FIDO(改)はそれぞれのサービス毎に鍵管理が必要、プラットフォーマーがその鍵管理を支えてリカバリーや新規端末利用時のユーザーの負担を軽減

プラットフォーマーが前面に出るか後ろで支えるか、みたいな話になりそうですね。

認証機能単体の利用サービスの実装負荷としてはFIDO(改)の方がApple/Google/MSアカウントそれぞれとID連携するよりは軽いでしょう。 上述の通り、ユーザーがプラットフォームを跨ぐ挙動(Android -> iPhoneに変更)した時にちゃんとサポートできるかは、もう少し詳しく考える必要があるでしょう。

プラットフォーマー以外は今後どうなる?

Yubicoなどのセキュリティメーカーはどうすべきかってところは気になりますね。 個人的にはプラットフォーマーの鍵管理にうまく接続できる(セキュリティキーでログインしたら鍵情報が端末やプラットフォーマーのアカウントに共有される)方向になるのかなーと思ったりします。

終わり

最近のFIDOの動向を、巨大プラットフォーマーとの関係という観点から見ていきました。 個人の業務としても自サービスへのFIDOの導入を検討してはいるので、こういう動向を踏まえて使いやすい状態を目指していきたいところです。

ではまた!

GNAP超入門 ~ OAuth 2.0との違いとは

こんばんは、ritouです。

この記事はDigital Identity技術勉強会 #iddance のアドベントカレンダー 初日の記事です。 今年も張り切っていきましょう。

qiita.com

さて先日、Internet Week 2021なるもののために GNAP についてお話する機会がございました。

speakerdeck.com

45分間で各自が話す持ち時間は10分のためさわりだけしかお話しできませんでしたが、資料を見てもなんのことやらって感じだと思うのでここでもう一回説明してみます。

f:id:ritou:20211201131746j:plain

f:id:ritou:20211130105239j:plain

Grant Negotiation and Authorization Protocol 通称GNAP(読み方はなっぷとかぐなっぷとかじーなっぷとか、人によって呼び方がフリーダムなID界隈の謎しぐさ)ですが、OAuth 2.0のこれまでの経験と知識を踏まえて新しい権限移譲のプロトコルを作っていこうという取り組みです。

現在策定中の仕様はコアプロトコルとリソースサーバーの2つです。

f:id:ritou:20211130105304j:plain

  • コアプロトコル : 権限委譲を行うところまで
  • リソースサーバー : リソースサーバーが認可サーバーとどうやりとりしてリソースアクセスを提供できるか

これだけ見るとRFC6749の仕様を分解したように見えますが、実際のコアプロトコルの仕様にはいろんなことがいっぱい書いてあります(雑)

www.ietf.org

今回は現時点のGNAP概要とOAuth 2.0との違いにフォーカスしつつ、概要を紹介します。

f:id:ritou:20211130105313j:plain

最初のIntroductionのあたりです。

f:id:ritou:20211130105324j:plain

最初に何が書いてあるかというと、このプロトコルはいわゆるClientインスタンス(OAuth 2.0でいうところのClientですね)がAuthorization Serverに対してアクセス要求をどう行い、必要な対話を行い、目的とする情報へのアクセス権限をえるところにフォーカスしていますということです。また、インターオペラビリティについても言及があります。

f:id:ritou:20211130105335j:plain

GNAPにおけるRole、いわゆる登場人物を紹介します。ほとんどOAuth 2.0と同じ用語になっていますが、最後のEndUserっていうのがClientを操作する人物のことです。

OAuth 2.0のAuthorization Code Grantのあたりでは、暗黙的にこのEndUserとROが同一人物である想定になっています。 GNAPでは色々なユースケースを想定している中で、これが別の人物になる可能性も考慮しています。 既存のものだと、CIBAとかを想像してみてください。CIBAのConsumption Deviceを操作する人物についてはあまり意識しないかもしれませんが、必ずしも人間ではない場合もありますし、OIDCにおけるEndUserと一致する必要もありません。

f:id:ritou:20211130105344j:plain

GNAPがさまざまなユースケースで使われていくにあたり、Role間のTrustrelationshipが重要になります。 先ほど言ったようにクライアントを操作する人物とアクセスしたいリソースのオーナーが異なる場合はいわゆるUser ManagedAccess、通称ウーマと呼ばれる仕組みのユースケースに近くなるでしょう。業務システムの承認フローみたいなのにも適用できそうです。

その他にもRole間の関係性がプロトコルのどの部分に関連するかが言及されていますので、興味のある方は仕様を読んでみてください。

f:id:ritou:20211130114915j:plain

シーケンスの説明のところで、いくつかフローがあります。

  • Redirect-based Interaction : ブラウザのリダイレクトを利用してROとインタラクションを行う、OAuth 2.0のAuthorization Code Grantのようなフロー
  • User-code Interaction : ユーザーコードを利用する、OAuth 2.0のDevice Authorization Grantのようなフロー
  • Asynchronous Authorization : EndUserとROが別で、処理が分かれて行われるOIDC CIBAのようなフロー
  • Software-only Authorization : ROとの対話が存在しない、OAuth 2.0のClient Credentials Grant のようなフロー
  • Refreshing an Expired Access Token : 期限切れのアクセストークンを更新する、OAuth 2.0の Refresh Token を用いた Access Token 再取得のようなフロー
  • Requesting User Information : RSへのアクセスを要求せず、ROの情報を要求する、OIDCのようなフロー

OAuth 2.0では RFC6749 でいくつかのGrantTypeが定義され、Authorization Code Grantは認可エンドポイントへのリダイレクトによりROとのインタラクションが行われます。それ以外にROとのインタラクションを要求するDevice AuthZ Grant や OIDC CIBA などは新規のエンドポイントへのリクエスト/レスポンスが定義されたりします。ClientはASが提供するフローのうち、自分の使いたいものを選択して実装する必要があります。

GNAPのシーケンスでは、様々なユースケースに対応されている中で、最初にClientからASにアクセス要求を行う部分は共通になっています。 その時に、Clientは自分が対応しているインタラクションを提示します。ASはアクセス要求の内容を検証し、必要なインタラクションをClientに応答することでその後のROとのインタラクションに誘導します。 ブラウザのリダイレクトが行われるのか、デバイスフローのような遷移が行われるか、CIBAのようなpush経由の同意が行われるのか、ROとのrインタラクションなしで処理が続けられるのかが分岐していくわけです。

f:id:ritou:20211130114926j:plain

最後にOAuth 2.0との関係を紹介します。プロトコルレベルでの互換性はありませんが、先ほど紹介した通り、OAuth 2.0/OIDCで提案されているユースケースは想定されていますし、OAuth 2.0では解決できない、複雑になってしまうようなところも拾っていくような感じです。

仕様内でも付録にOAuth 2.0との違いが記載されております。Appendix B. Compared to OAuth 2.0" です。

f:id:ritou:20211130114937j:plain

ここでは6つの観点から整理されています。

  • Consent and authorization flexibility: OAuth 2.0(RFC6749)はブラウザでのインタラクションが定義されていますが、GNAPではDevice AuthZ GrantやOIDC CIBAのようなインタラクションまでサポートしています。
  • Intent registration and inline negotiation: これはシーケンスのところでも説明したように共通のアクセス要求から始まって、そこでネゴシエーションが行われるよってところです。
  • Client instances: OAuth 2.0において、ClientにはClientIDが割り当てられますが、GNAPはClientからASに提示された鍵の情報をうまく使います。
  • Expanded delegation: OAuth 2.0のscopeがresourceやRARという概念で拡張されていますが、GNAPは最初からRARのような柔軟な表現が使われています。
  • Cryptography-based security: OAuth 2.0ではClientSecretというBearer Secretがいろんなところで使われていますがFAPIなどでは別の方法が必要になります。GNAPではClientが保持する鍵を用いた暗号ベースの保護を利用します。
  • Privacy and usable security: OAuth 2.0はAS-RSの強力な紐付けが前提となっていますが、GNAPでは色々なパターンでAS/RSがやり取りできるように考えられています。

ざっとこんな違いが書かれています。

f:id:ritou:20211130114947j:plain

GNAPの最新情報はIETFのWG、ML、Githubを参照指定いただければと思います。

今回は超入門として、OAuth 2.0とGNAPの設計思想の違いを紹介しました。 この辺りを先に意識した上でGNAPのプロトコルの詳細を見ていくことで今までよりも読んでいくハードルを下げられるのかなと思ったりします。 今回な具体的なリクエスト/レスポンスには触れませんでしたが、続きの形でOAuth2.0と比べながら細かく見ていく記事も書こうと思っています。

また、まだまだ使われ続けるOAuth 2.0の各種フローをそのまま覚えるのでなくROとのインタラクション、トークン発行、クライアントとの紐付けやクライアント認証といった部分を細かいパーツに分けて理解し、GNAPはそれを組み替え直したようなものだと捉えられるようになったらもうこの辺の新しい仕様も怖くなくなります。これからも頑張りましょう。

明日のアドカレはAuthleteのあの人が記事を書いてくれそうです。楽しみですね! 私は今のところ、最終日に何かを書く予定です。 ではまた。

10/27に "iddance Lesson3 TwitterのOAuth 2.0とかCIBAの使い所を知ろうの会" やります

f:id:ritou:20211014102025p:plain

おはようございます、ritou です。 2週間ぐらい後に、こんな勉強会をやります。

idance.connpass.com

聞く対象としては初学者〜慣れてきたぐらいの人、話す方は使ってみた/本書いた/仕事で実装してみたぐらいの人を想定して開催してた勉強会ですが、オンラインでやるのどうなんかなーと考えつつしばらく間が空いてしまったのでまだ3回目ですが今回はオンラインでやります。

お話の内容は記載してあるとおりですが、静かに聞いて終わりってのよりは良くも悪くもガヤガヤしてる方が参加者/発表者のためになると思います。 もし今回の内容に関するこれが聞きたい、気になってるみたいな話や今後聞きたい話とかがあったらTwitterハッシュタグ #iddance つけてTweetしてもらうか、DiscordのServerに来てもらえたらと思います。

ぜひ参加してみてください。 ではまた!

OAuth 2.0等のRFC番号が飛び交う会話についていくためのカード [PR]

f:id:ritou:20211004023232j:plain

こんにちは、ritouです。

私は以前からこういうTweetをしていました。

このような日々の努力の結果、いわゆる「RFC番号が飛び交う会話」を身に付けたのです。

そして、このハードルを初学者が乗り越えていくために何ができるかを考えました。

その結果思いついたのが「ID-RFCカード」です。

r-weblife.booth.pm

何かというと、まず、RFC番号とタイトルの組み合わせをバーっと並べた「仕様カード」があります。

f:id:ritou:20211004023400j:plain

そして、RFC番号とざっくりな概要の組み合わせをバーっと並べた「解説カード」があります。

f:id:ritou:20211004023429j:plain

対象のRFCIETF

  • OAuth
  • JOSE
  • COSE
  • SCIM

のWGで仕様策定されたものと、WebFingerで合計41個です。

使い方も切って使うなりそのまま眺めるなり、無限にあります。

この「ID-RFCカード」で知識を蓄え、「RFC番号が飛び交う会話」に参加できるようになりませんか?

よろしくお願いします。

ではまた!

ID連携の標準化仕様紹介とセキュアな実装のためのアプローチ ~ 2021

f:id:ritou:20210904174248p:plain

おはようございます ritou です。
久々に「解説付きスライド全公開」的なやつをやります。

f:id:ritou:20210904174413p:plain

先月、チーム内でID連携のための標準化仕様に関する勉強会(私が一方的に話す会)を行いました。
が、実際はだいぶグダグダになってしまい、これはその後色々付け足してるうちに別物になってしまった資料です。

f:id:ritou:20210904200923p:plain

内容としては、ID連携のための標準化仕様にどのようなものがあるかを知ってもらうための「入門編」のような立ち位置で作りました。

f:id:ritou:20210904200944p:plain

OpenID Connect(SAMLのような) ID連携のための標準化仕様を紹介しようと思うと、ついつい個別にシーケンスやリクエスト/レスポンスの説明を始めがちですが、初学者が気になるのはそんな細けぇことではないでしょう。 まずは「この仕様で何ができるようになるのだろう」「この仕様では何を実現したいんだろう」と言うところから理解していくのが良いのではないでしょうか。

そこで、今回はこれらの仕様を知るきっかけとして、

  • たくさんある仕様のうち、"何かをできるようにするための仕様" の概要
  • "セキュリティ強化のための仕様" で使われていてID以外のWebアプリケーション開発に活かせそうな考え方

を紹介していきます。

ID連携機能を提供するための仕様とユースケース

f:id:ritou:20210905005548p:plain

前半は「こんなことを実現するための標準化仕様がありますよ〜」って言うお話をします。

OpenID Connect

f:id:ritou:20210905005709p:plain

いきなりですが、OpenID Connectの話を(ちょっと)します。

f:id:ritou:20210905012239p:plain

まずは、よくある「●●でログイン」って言う機能の流れを紹介します。

ステップ1として、Relying Party(以下、RP)という「外部アカウントを利用してログインさせるサービス」のログイン画面にて、ユーザーが「●●でログイン」と言うボタンを選びます。

f:id:ritou:20210905012422p:plain

ステップ2として、RPがOpenID Provider(OP)にユーザーを送ります。 画面のリダイレクトやポップアップなどで実現されます。

f:id:ritou:20210905012448p:plain

ステップ3,4では、OPがユーザーに対して認証を要求したり、「RPに対してあなたの情報を提供することを許可しますか?」と言って確認を求めます。

f:id:ritou:20210905012514p:plain

ステップ5では、ユーザー情報と合わせて「いつ、誰に対してどんな情報を提供することに同意を得たか」と言った認証イベントの情報が提供されます。

f:id:ritou:20210905012539p:plain

ステップ6では、RPは受け取った認証イベントの情報を検証して紐づくユーザーをログイン状態にします。

f:id:ritou:20210905012747p:plain

このような「●●でログイン」とう機能を実現するための標準化仕様がOpenID Connect(OIDC)であり、OPがユーザーの同意の元でRPに認証イベントの情報とユーザーの属性情報を提供するための仕組み です。

f:id:ritou:20210905012826p:plain

OIDCのコアとなる仕様ではここまで説明した最低限の機能である

  • 認証イベント情報の受け渡し方
  • ユーザーの属性情報の提供方法

が定義されています。

それ以外にもいくつか仕様があるのですが一部は後から紹介します。

OAuth 2.0

f:id:ritou:20210905013037p:plain

次に紹介するのが、OAuth 2.0です。

f:id:ritou:20210905013120p:plain

あるサービスが別のサービスのユーザー情報にアクセスする「データ連携」の流れを紹介します。

ステップ1として、ユーザーはブログのようにClientと呼ばれる「外部サービスのデータを利用するサービス」で、別サービスで管理するリソースの利用を開始します。(例えばログイン状態の設定画面の「●●と連携する」ような機能から処理が始まることが多いでしょう。)

f:id:ritou:20210905013749p:plain

ステップ2として、ClientがAuthorization Server(以下、AS)にユーザーを送ります。

f:id:ritou:20210905013813p:plain

ステップ3,4では、ASがユーザーに対して認証を要求してResource Orner(RO)を特定し、「Clientがあなたの情報にアクセスすることを許可しますか?」とリソースアクセスの許可を求めます。

f:id:ritou:20210905013847p:plain

ステップ5では、ASからClientに対してAPIアクセスのためのトークン発行処理が行われることでリソースアクセスの権限を提供します。

f:id:ritou:20210905013943p:plain

ステップ6ではClientが取得したリソースアクセスの権限を利用してAPIアクセスを実行できる状態、つまり「連携済み」の状態となっています。

f:id:ritou:20210905014208p:plain

このようなデータ連携を実現させるための仕様がOAuthです。 一言で説明すると、認可サーバー(AS)がリソースオーナー(RS)の同意のもと、クライアントに対してリソースアクセスを提供するための仕組み です。

OIDC vs OAuth 2.0

f:id:ritou:20210905014241p:plain

と、OIDCとOAuth 2.0の両方を並べて説明したところで "なんか似てる" と思われた方もいるでしょう。 登場人物の呼び名が違うものの、一連の流れは同じような感じでした。

f:id:ritou:20210905014327p:plain

で、この辺りの話をするときに避けられないのがいわゆる "OAuth認証"と言うワードです。

「データ連携を提供する仕組みであるOAuthを用いても、リソースオーナーの情報にたどり着くことができたらそのユーザーでログインさせられるよね。」

この考えがOAuth認証と呼ばれるものの正体です。

Client -> AS -> Clientという一連の流れでリソースアクセスを許可したリソースオーナー本人を特定してログインさせることができれば問題はありませんが、少しでもその流れが保証されないような仕組みになっていると問題が起こり得ます。

ritou.hatenablog.com

それに対して、OIDCではRPは一連のフローで取得した認証イベントの情報を利用してユーザーをログインさせます。少しだけ、この認証イベント情報について触れましょう。

f:id:ritou:20210905014404p:plain

OIDCの仕様を見たことがある方はIDトークンと呼ばれているものをご存知でしょうか。

  • いつ (トークン発行日時)
  • 誰が (ユーザー)
  • 誰に (RP)
  • 誰が持つ (OP)
  • どんな属性情報を提供することを (scope)

という5W1Hのような情報、あとはセキュリティ対策のためにRP -> OP -> RPと引き回される値などを含んだものが認証イベント情報です。

後は継続的にユーザーの属性情報を利用したいサービスのために専用のAPI仕様が定義してあるのがOIDCです。

ちょっと昔話をすると、この属性情報APIへのアクセスも独自に仕様策定が進められていましたが、仕様策定当時に同様に策定が進められていた OAuth 2.0 と目的が一致していたため、OAuth 2.0をベースとしてこのようなIDレイヤーに関する仕様を加えて拡張したものとして仕様策定を進めた という関係性になっています。

OIDCで扱う属性情報についても少しだけ触れておきましょう。

f:id:ritou:20210905014432p:plain

OIDCでは汎用的なプロフィール情報、あとはメールアドレスや電話番号は確認済みフラグの定義もあります。

f:id:ritou:20210905014457p:plain

最新の属性情報を返す "UserInfo Endpoint" から返されるJSONの表現例を載せておきます。 住所も細かく分かれてるのとか連結したやつとかが定義されています。

よくこの手のフォーマット見て 「日本にはな、「カナ」「漢字」と言うのがあるんじゃ。どうすりゃいいのよ。」 みたいなことがありますが、実はカタカナや漢字の氏名もサポートされています。

5.2. Claims Languages and Scripts

Human-readable Claim Values and Claim Values that reference human-readable values MAY be represented in multiple languages and scripts. To specify the languages and scripts, BCP47 [RFC5646] language tags are added to member names, delimited by a # character. For example, family_name#ja-Kana-JP expresses the Family Name in Katakana in Japanese, which is commonly used to index and represent the phonetics of the Kanji representation of the same represented as family_name#ja-Hani-JP. As another example, both website and website#de Claim Values might be returned, referencing a Web site in an unspecified language and a Web site in German.

(日本語訳)

Human-readable な Claim Value およびそれらへの参照は, 複数言語および複数文字種で表現することができる (MAY). 言語および文字種の指定には BCP47 [RFC5646] 言語タグを # を区切り文字としてメンバー名に付与する. 例えば family_name#ja-Kana-JP は日本語のカタカナ表記の Family Name を示す. カタカナ表記の氏名は, 漢字表記 (family_name#ja-Hani-JP) の氏名の発音を示したりインデックス目的等で用いられることが多い. 他にも website が言語指定のない Web サイト, website#de がドイツ語の Web サイトを参照していることを示すといった例も挙げられる.

  • family_name#ja-Kana-JP : カナ 姓
  • given_name#ja-Kana-JP : カナ 名
  • family_name#ja-Hani-JP : 漢字 姓
  • given_name#ja-Hani-JP : 漢字 名

Yahoo! JAPAN の属性取得API(UserInfoAPI)のドキュメントにも書いてあります。

developer.yahoo.co.jp

個人的にはこの辺りがあるおかげで普段個別で持ってたデータも汎用的に扱えて便利そうだなーと思っています。

f:id:ritou:20210905014528p:plain

ここまでざっとOIDCの概要について説明しました。 実際は関連する仕様がいっぱいあるので、引き続き「これができるようになる仕様がある」みたいな話をしていきます。

Discovery & Dynamic Registraion

f:id:ritou:20210905014914p:plain

まずはOIDCのDIscoveryとDynamic Registrationという仕様の紹介です。

f:id:ritou:20210905014943p:plain

こちらはよくあるソーシャルログインの画面ですが、このボタンってどうやって設定するかわかりますか?一般的なのはサービスが対応するOPを厳選して開発者登録とかして最終的に設定完了したら並ぶみたいな流れでしょう。

f:id:ritou:20210905015001p:plain

Discoveryというのは、RPがエンドユーザーの情報からOPを特定する方法、OPがエンドポイントや仕様のサポート状況を外部に公開する方法が定義されています。

OP特定の方はメアドやURLとかからOPのURLを教えてくれという要求を行うために、 WebFinger と呼ばれる仕組みを利用します。

外部公開の方は、OPは OpenID Configuration と言って特定のパス /.well-known/openid-configuration にて静的なJSON形式のデータを返します。 こちらは GoogleYahoo! JAPAN などいろんなサービスが対応しています。

Dynamic Registrationの方は、動的にRPをOPに登録する方法が定義されています。

f:id:ritou:20210905015048p:plain

GoogleのOIDC関連ドキュメントから拾ってきた図です。JSON形式で各種エンドポイントからサポートする仕様、属性情報とかを提示できます。

f:id:ritou:20210905015106p:plain

Dynamic Registrationのリクエスト/レスポンスの例です。 今日は概要紹介なので詳細は省きます。

f:id:ritou:20210905015123p:plain

これらの仕様のユースケースとして、RP設定の効率化、自動化、半自動化みたいなものがあります。

これを実装しているサービスもいくつか聞いたことがあるのでもしかしたら体験された方もいらっしゃるかもしれません。 OIDCを利用するRPの立場になりうるサービスの設定画面で "Googleと連携する" とかを押すと、エンドポイントの設定がバシッと終わっちゃうみたいなイメージです。

ここでOPがDynamic Registrationに対応していたらRPの登録まで一気に終わっちゃうのでしょうけれども、大体はここは非対応で開発者がOPにredirect_uriや名前などを登録して取得したClientID/ClientSecretの値をRPに登録する場合が多いです。

という感じで、半自動化~自動化もできるようになりますよってところですね。 Discoveryの中でもOpenID Configurationの部分は対応しているOPが多いので、このユースケースは割と一般的になりつつあるのかなってところです。

f:id:ritou:20210905015143p:plain

もう一つのユースケースはさらに実装してるところ見かけないやつですが、 "ユーザーが普段利用しているOP" でログインできるようなものです。 これはOpenID Connectの前のOpenID Authenticationと呼ばれる仕様で採用されていたUX であり、メールアドレスの @以降の部分からOPを特定、今まで使ったことのないOPであれば OpenID Configurationの値を取得してDynamic Registrationするという流れです。

リソースアクセスをターゲットにしているOAuth 2.0の本来のユースケースだとどうしてもベンダーロックインな仕組みに思えますが、OpenIDは本来 "User-Centric Identity"つまり、ユーザーが自分の使いたいOPを選んでID連携機能を利用することを想定した仕組みです。

現状ではDynamic Registrationに対応しているOPがほとんどいないと思うのでこのユースケースはなかなか使われないと思いますが、こういうこともやろうと思えばできるよというお話でした。

Session Management

f:id:ritou:20210905015410p:plain

次はセッション管理に関する仕様を紹介します。

f:id:ritou:20210905020110p:plain

OIDCを用いたID連携において、基本的にはOP/RP間のセッション状態は同期されません。 もちろん、ID連携した瞬間というのはOP/RP共に同じユーザーが利用しているので共にログイン状態となりますが、その後OP/RPのいずれかでログアウトしたらその状態は崩れます。 また、OPと複数のRPが連携している場合、RPごとに同じことが起こり得ます。

f:id:ritou:20210905020131p:plain

そのような状況を改善するために、セッション管理に関する仕様があります。

  • Session Management : この仕様ではOP/RP間でセッション状態が同期しているかを確認する方法を定義しています。具体的には、iframe + postMessageを用いて状態変更がないかどうかを問い合わせます。
  • RP-Initiated Logout : RPからOPにログアウトしたい意思を伝える方法を定義しています。OPの専用エンドポイントにリダイレクトさせることで実現します。
  • Front Channel Logout : OPがフロントエンドのリクエストなどを用いてRPにログアウトする意思を伝える方法
  • Back-Channel Logout : OPがバックチャンネルのリクエストを用いてRPにログアウトする意思を伝える方法

後者2つは昔から "Webビーコン" でやるとか、バックエンドでやるのは確実だけどシステム作るのめんどいなーとか言われてたものを標準化した感じです。

必要に応じて、これらとOIDCを組み合わせて使っていくイメージです。

f:id:ritou:20210905020154p:plain

これらの仕様により、何ができるようになるかというと

  • 複数ドメインで構成される単一サービスをOP/複数RPの関係と捉え、OP上のユーザーのログイン状態に変更がないかをRPは随時問い合わせることができる
  • また、OPのログイン状態に変更があった場合、RP全てをログアウト状態にできる

というあたりです。

前者の例は複数の国に対応するサービスにおいて、トップレベルドメインが異なるURLを複数持つサービスがある場合とかですかね。 とはいえ、最近の3rd Party Cookieに関するブラウザ実装の方針によってはこれらの仕様のうち postMessage や iframe 周りの挙動が変わるかもしれないため、導入を検討する際は注意が必要です。

Identity Assurance

f:id:ritou:20210905020313p:plain

次は Identity Assurance という仕様を紹介します。

f:id:ritou:20210905020335p:plain

なんとかPay、なんとかgramといった決済アプリではもはや馴染みのある "犯収法" "本人確認" といったキーワードですが、この本人確認を行った情報の流通についてのお話です。

自分たちで実装、運用していくとなかなか手間のかかる本人確認 ですが、せっかくならこの確認済みの情報や確認済みフラグといった情報をユーザーの同意のもとで外部サービスに提供しようと思うとどうなるでしょうか?

f:id:ritou:20210905020414p:plain

Identity AssuranceはOPで検証済されたユーザーの属性情報をRPに提供するための仕様です。

"どのルールに従った情報が欲しい" という "要求", "誰がどのルールに従って検証したか" という検証プロせるの情報と値を組み合わせた"表現"、ID連携時のみに提供するかUserInfo Endpointから最新の情報を返すかといった"応答"の仕様が定義されています。

f:id:ritou:20210905020445p:plain

OIDCの属性情報は上で説明しましたが、属性情報の "値" だけでした。 Identity Assuranceでは "誰がどのルールに従って検証したか" という "verification" の情報と属性の値 "claims" によって表現されます。

ちなみにこの "trust_framework" の部分の定義に日本の

も含まれており、"これは犯収法対応のために検証済みの情報です" といった表現が拡張なしで利用できることを意味します。これは国内企業の方が仕様策定に関わることで実現されました。

f:id:ritou:20210905020510p:plain

ユースケースもそのままですね。

  • OIDC : 個別のユーザー認証 -> 認証イベントを流通
  • Identity Assurance : 個別の属性情報検証 -> 検証済み属性情報を流通

という感じで、OPが各種書類やマイナンバーカードで検証した情報の流通はこれから重要になる仕組みのための仕様です。

RISC & CAEP

f:id:ritou:20210905021544p:plain

次は RISC と CAEP という仕様です。

f:id:ritou:20210905021626p:plain

OIDCのフローで説明したID連携処理を行う際、ユーザーは認証処理やアクセス元など含めたリスクベース判定などで安全だと判断されることが一般的です。

しかし、その後例えばネイティブアプリが動作しているスマートフォンやPCのネットワーク環境が変わったりした場合のことを考えると、リソースアクセス時のリスク評価も必要となるでしょう。また、ID連携後にユーザーがOP上でBANや不正利用などでアカウント停止に陥った時、ログインのみに利用しているRPなどはそれを知ることなくサービスを提供し続ける可能性があります。

f:id:ritou:20210905021654p:plain

このような状況を考慮して、OpenIDファウンデーションのWGでは

  • CAEP : 継続的なアクセス評価
  • RISC : アカウントの状態変化を通知

という機能のための仕様策定が進められています。 同時に、前者はmicrosoft、後者はGoogleが実際にプロダクトに導入するなどリファレンス実装も進められています。

f:id:ritou:20210905022349p:plain

RISCの例として後者のRISCユースケースをあげると、ID連携を行ったOPからRPに

  • アカウントの状態変更、退会
  • セッションの無効化

などのイベントをWebhookでJWTを送ることで通知、RPが署名検証して対応する処理を実施のようなことが実現できます。

CIBA

f:id:ritou:20210905021745p:plain

ユースケースを伴う仕様紹介の最後は CIBA です。

f:id:ritou:20210905022430p:plain

最近個人的に注目している、Decoupled Authentication という概念があります。

いわゆる "手元のスマホで認証 + アルファする" っていうお話で、決済の場合は 3D Secure 2.2 からリスク判断結果に応じて手元の端末にPushが送られるフローが導入されています。

このような「手元のスマホでID連携」を実現するための仕組みが CIBA です。

f:id:ritou:20210905022453p:plain

CIBAでどのようにこの Decoupled Authentication を実現するかという点で大事なのが、環境の分離です。

これまで紹介したOIDCのフローではユーザーが操作する User-Agent つまりブラウザが "認証を要求する端末" であり "認証を行う端末" でした。

しかし、CIBAではこれを

  • 認証を要求する端末がユーザーの目の前にある必要はない
  • 認証を行う端末が手元にあり、そこにPush通知などで要求が行く

というように分離します。 認証を行う端末がスマホなどになることで、生体認証やローカル認証を利用するFIDOとの相性も良くなり、より利便性の高いUXの提供にも繋げられるかもしれません。

f:id:ritou:20210905022518p:plain

CIBAのユースケースで重要なのは、事前にユーザーをある程度識別する必要があるところです。 コンビニ決済の例では、客がポイントカードを出す際にそれに紐づくアカウントを店側で保存しておけば「この人に幾らの決済をさせたい」と言う要求ができます。

手元で通知を受け取って決済内容に同意したら、どこぞのQRコード決済のように店員に見せたり端末にスマホをかざして「なんとかぺ〜い」って聞こえるまでの微妙な時間を待つ必要もありません。

f:id:ritou:20210905022547p:plain

CIBAのユースケースとしては先ほどのコンビニに限らずPOS端末全般にNFC端末を置かずに導入できたり、コールセンターや銀行窓口での認証+アルファの確認に使ったりするユースケース、あとはサブスクライブ決済の定期的な確認要求といったところを想定しています。

ここまでのまとめ

f:id:ritou:20210905022612p:plain

前半部分ではID連携に関連する標準化仕様のうち「こんなことができるよ」ってのがはっきりしているものを紹介しました。

ID連携なんて言っても●●でログインだけなんでしょ?と思われている方もいるかと思いますが、実際はその周辺機能についても「この機能も併せて使うよね。なら標準化しよう。」と言った流れでどんどんと拡張されています。

標準化が行われるメリットとしては、ユースケース毎にリスクなどを整理された上で仕様策定が進められることが一番でしょう。 みなさんご存知の通り、ID連携に関わる脆弱性はサービス全体の将来を左右するものになり得ます。 これをやりたい!と思いついたことが既に標準化されていないかを確認し、要件が一致するものは積極的に利用していくことが重要です。

ID連携仕様から学ぶWebアプリケーション間のデータ送受信をセキュアにする方法

f:id:ritou:20210905023446p:plain

後半戦です。

ここからは、ID連携のための標準化仕様に出てくる "Webアプリケーション間のデータ送受信をセキュアにする方法" を紹介します。

標準化種類のタイプ

f:id:ritou:20210905024825p:plain

ID連携のための標準化仕様は

  • 基本仕様 : プロトコルというよりもベースとなるフレームワークや要件を満たす最もシンプルな仕様である程度拡張性を持たせたもの
  • 拡張機能 : 既存仕様と組み合わせたり、一部を置き換えることで安全性を高められる "武装" のような立ち位置にある仕様
  • 特定のユースケースではここに気をつけなれけばならない、と言ったプロファイルやベストカレントプラクティス、あとは脅威と対策の整理したやつとか

というような種類に分けられます。

今回はその中で、拡張仕様でよく使われるテクニックに注目します。

f:id:ritou:20210905024843p:plain

例えば OAuth 2.0 に関連するものは IETF にて仕様策定されてRFCとして発行されているのですが、

f:id:ritou:20210905024904p:plain

ちょちょっと検索して出てくる "RFC 6749 The OAuth 2.0 Authorization Framework" がベースとなるフレームワーク、"RFC 6750 The OAuth 2.0 Authorization Framework: Bearer Token Usage" っていうのが最もシンプルなアクセストークンの種類の定義となります。

それに対してモバイルアプリとかでトークンを安全にやりとりできないケースがあるとなった時にピンポイントで強化する "拡張仕様" が "RFC 7636 Proof Key for Code Exchange by OAuth Public Clients" です。

f:id:ritou:20210905024951p:plain

そして、モバイルアプリやSPAがOAuth 2.0を実装する時に気をつけないといけない細けぇお話みたいなのが BCP としてまとめられています。

その結果、全体で見た時に拡張仕様、とBCPがもりもりになってしまってカオスな状態になってしまい、「新しい話入れずに、一旦まるっと整理しようか」という立ち位置で仕様策定されているのが "OAuth 2.1" と呼ばれているものだったりします。

f:id:ritou:20210905025020p:plain

OpenID Connectの方でもOpenIDファウンデーションにてFAPIというWGが作られています。

f:id:ritou:20210905025528p:plain

元々金融サービス向けのプロファイル作成を目的として作られたのでFAPI("Financial-grade API")という名前になっていますが、ヘルスケア分野のセンシティブなデータのやり取りなどの基準としても使えるようにということで中身は「大事なデータを扱うユースケースにおけるセキュリティプロファイル」と言ったところになっています。

最初は銀行APIの "残高読み取りだけ" = Read Only, "預け入れなども行う" = Read & Write みたいな立ち位置だったのが、"Baseline" と "Advanced" という名前でプロファイルの仕様が分けられています。

f:id:ritou:20210905025702p:plain

ここからは、FAPIでも言及されている仕様で使われている、"Webアプリケーションをセキュアにするための実装のヒント" を紹介します。

と言うと、なんかとても難しい話になりそうですが、実際はそんなことはありません。

直接通信と間接通信

f:id:ritou:20210905025724p:plain

OIDCやOAuth 2.0の仕様を分解していくと、サービス2者間、もしくはユーザーが絡んだ3者間のデータのやり取りです。 それらはアプリケーション間の直接通信、ブラウザが絡んだ間接通信と呼ばれています。

直接通信には "誰のリソース(属性情報)" と言う概念がなくアクセス元が何者かだけを検証するための "クライアント認証" を可能とするリクエストと、"誰のリソース(属性情報)" と言う概念が入った "アクセストークンにより保護された" リクエストがあります。

間接通信では、ブラウザを利用した一般的な GET/POST のリクエストがよく使われています。

直接通信

f:id:ritou:20210905025824p:plain

まず、クライアント認証と呼ばれるところから見ていきます。

"Googleでログイン" みたいな機能を実装する際に、Googleから

  • クライアントID
  • クライアントシークレット

を発行してもらって、Webアプリの環境変数に設定して...みたいな作業を行ったことがあるでしょう。それぞれは識別子とパスワードみたいなものです。

この正しいシークレットを保持していることを確認する仕組みが共有暗号方式、RSAやECDSAなどの秘密鍵/公開鍵のペアを作成して公開鍵を相手に渡して...ってやるのが公開鍵暗号方式と呼ばれていますが、クライアント認証でもこれらに相当したいくつかの方式があります。

f:id:ritou:20210905025852p:plain

最もシンプルなのが、クライアントシークレットをリクエストにそのまま含んで受け取った側が正しいものかどうかを検証する方式です。

  • POSTのリクエストボディのパラメータに含む ("client_secret_post")
  • Authorization ヘッダにBasic認証のパスワード相当として含む ("client_secret_basic")

と言うものですが、プロキシその他なんらかの方法でリクエスト自体を取得されるとクライアントシークレットの値を確認でき、第3者になりすましをされる可能性があると言うリスクもあります。

それを少しセキュアにしようとすると、"そのままじゃなくて署名生成の鍵として使おう" と言う方式です。("client_secret_jwt") リクエストにクライアントシークレット自体が含まれないため、リクエスト自体を取得するだけでは第3者にクライアントシークレットの値を把握されることは困難になります。

f:id:ritou:20210905025917p:plain

さらに公開鍵暗号の仕組みを利用すると、リクエストを作成できるのが秘密鍵の所持者のみ、となります。 これの何が便利かと言うと、何かあった時に「あなた、このリクエストを確かに送りましたよね」と言う証拠として扱えることです。いわゆる否認防止(Non-repudiation)と呼ばれるものであり、センシティブなデータを扱う分野だと重要になってきます。

また、アプリケーションレイヤではなくTLSのレイヤでこれらを実装する方式もあります。Mutual TLSなんて呼ばれていますが、TLSのクライアント証明書を利用するものです。署名生成/検証処理をTLSに任せ、アプリケーションレイヤでは鍵の所持者が適切かと言うあたりを検証するイメージですが、最近はAzureのなんとかやAWSのなんとかで対応が進んでいたりします(説明が雑)ので今後はやってくるかもしれません。

f:id:ritou:20210905025947p:plain

アクセストークンを用いたリソースアクセスの保護については、2種類に分けられます。

  • "Bearer Token" と呼ばれる、アクセストークンを持っているものがその権限を持っていると言う仕組みです。現実世界では電車の切符などがそれに当たります。拾った切符でも有効だったら電車に乗っていけますね。
  • "Sender-Constrained" と呼ばれる、トークンを発行された対象であることを検証可能な仕組みです。海外の航空チケットの場合、(本人と顔写真などで紐づけられた)パスポートとの関連を確認することで搭乗が許可されます。

"Sender-Constrained" の方がセキュアであり、それを実現するための仕組みとしては先ほど紹介したMutual TLSを用いるものや署名付きのJWTを利用するものがあります。

f:id:ritou:20210905030008p:plain

直接通信のポイントをまとめます。 まず、そう簡単に暗号化、Encryptionまでは要求されません。これは割と大事なことです。 クライアントシークレットを利用する仕組みでも、値そのものを送るのではなく署名生成の鍵として利用する方がセキュアである。 そして、トークンを利用する場合も、"Sender-Constrained" にすることでリクエストをセキュアなものにできます。

間接通信

f:id:ritou:20210905030031p:plain

次に間接通信です。 URLをブラウザで開く、でおなじみのGETアクセスですが、OIDCやOAuth 2.0では3種類ぐらいの方式が利用されています。

クエリパラメータを使うのは最もシンプルな方法ですが、ユーザーが手元で値を改竄できたり、サーバーにそのままログが残ってしまうことが懸念としてあります。

OIDCやOAuth 2.0の各種トークンがログに落ちるとそれだけで "漏洩した" となってしまう可能性があるので、GETで重要な値をやりとりする場合はフラグメント部に指定する方式をとります。 "https://example.com/hoge#foo=bar" みたいなやり方ですね。こうするとサーバーに値は落ちないのですが、リダイレクトするときにブラウザがこの値を引き回すかどうか、引き回した時の脆弱性が一時期話題になったりしました。

そして、こちらでも署名付きのJWTにしたら改竄されても検知できるじゃんと言うお話が出てきます。

f:id:ritou:20210905030052p:plain

サーバーにログを残したくないやり方として、POSTでリクエストを送る方法もあります。 個人的には簡単に値をいじってリクエストを送るのがちょっと手間になるのでアレなのと、Microsoftの仕組みはPOSTのリクエストが好みなようです。

こちらも値をそのまま送るのと、署名付きJWTにして送る方式があります。 ちなみにドメインまたぎのPOSTリクエストは受け取った時にHTTP Cookieが送られてこないみたいな3rdParty Cookie, SameSite属性と言ったあたりの話が出てくるので注意が必要です。

f:id:ritou:20210905030146p:plain

OIDCの認証要求を複雑にしたり、認証と同時に決済の要求をしようとしたり...なんてことを考え始めると、どんどん送りたいパラメータが増えていきます。そしてそれをJWTなんかにしたら文字列のサイズが...となることがあるため、直接通信と間接通信の組み合わせもよく使われます。

ここで言うPush/Pullはデータの流れだけを意味していて

  • Push : 先にデータを直接通信で送っておいてそのキーを間接通信に含むやり方
  • Pull : キーとなる値を間接通信で受け取り、受け取った側が直接通信でとりにいくやり方

と言う2種類があります。

なんで両方あるかと言うと、OIDCでの直接通信が

  • RP -> OP 多用されている
  • OP -> RP あまり利用されない

みたいな特徴があるためです。

開発中にlocalhostで立ち上げたRPのWebアプリを考えてみてください。そこからOPに直接通信を送るのは簡単ですが、OPからのリクエストを受け取ることはできません。

一方、間接通信はブラウザがあれば可能です。 最近のゼロトラストの流れで「トラストの境界はネットワークからIDに変わった」なんて言葉を聞くことがあるかもしれませんが、以前はネットワーク制限で直接通信が不可能な場合に間接通信だけで実装可能な仕組みが使われきたんだ、とSAMLに詳しい先輩方に教わりました。OIDCでも同じことができます。

さらにこれをJWTにするのも当然ありです。

f:id:ritou:20210905030220p:plain

あとはちょっと応用として、ID連携においてはRP -> OP -> RPと言う往復、海外では "OAuth Dance" と呼ばれたりしている挙動が出てきます。 2つの間接通信が絡むため、CSRFや値の横取りなどで忙しい世界線です。

f:id:ritou:20210905030249p:plain

このような場合、RP側でセッションと紐づけた値をリクエストに含み、レスポンスを検証する必要が出てきます。 これもJWTと組み合わせることで改竄検知が可能になります。

f:id:ritou:20210905030309p:plain

連続した間接通信について、OIDCやOAuth 2.0ではこれらのパラメータで保護することが可能です。この辺りで検索すると私のブログが引っかかると思うのでよろしくです。

f:id:ritou:20210905030345p:plain

間接通信について紹介しましたが、こちらもそう簡単に暗号化までは求められません。 改竄されたくなかったら署名付きJWTを使う、露出させたくなかったら直接通信で送れと言う感じです。

f:id:ritou:20210905030406p:plain

まとめ

後半のまとめです。 OIDCのセキュリティプロファイルで指摘されている拡張機能で使われている、直接通信と間接通信をセキュアにする仕組みを紹介しました。

f:id:ritou:20210905030429p:plain

ID連携のための標準化仕様のうち、拡張仕様と呼ばれる仕様のいくつかは既存の仕組みのどこかをこの辺りのテクニックを用いて強化するものです。 今回紹介したあたりを意識しておくと、今後もし他の標準化仕様を読む機会ができても怖くなくなるかも...と思います。 もし次回があれば、実際のID連携の細かい処理単位で解説、ってのもしたいなと思ってます。

まとめ

f:id:ritou:20210905030429p:plain

ID連携のための沢山の標準化仕様のうち、今回は「何かをできるようにするための仕様」と「既存の仕様の一部を拡張してセキュアにするための仕様」に着目し、 ゆるふわな感じで 紹介しました。

前半について、ID連携というと「〇〇でログイン」の部分だけ、せいぜいそれとAPIアクセスぐらいを想像される方が多いかと思いますが、関連機能も標準化は進められています。 「あんなこといいな できたらいいな」なんて言う要望を叶えてくれるのはどこぞのポケットではなく標準化活動であると言えるでしょう。

後半について、この分野の仕様を調べていくとどんどん関連する仕様が出てきて頭が混乱しそうになることがあります。(みんなそうです。そしてTwitterで「OAuthなんもわからん」「OIDCなんもわからん」と書いて私の知り合いに本を薦められるのです。) しかし、基本的に通信をセキュアにするための実装パターンは限られており、それをベースとなるシーケンスのどこにどう導入するかというのが拡張仕様そのものになっていたりします。 言ってしまえばID連携なんてのはユーザー情報がメインのサービス間データ連携であり、「枯れた」と表現されることもある定石テクニックが仕様に採用されているわけなので、それを普段のアプリケーション開発に活かすことは難しいことではないでしょう。

以上です。

f:id:ritou:20210905030450p:plain

スライド

speakerdeck.com

超参考になる資料

www.amazon.co.jp

みんなで読みましょう!

ではまた!

NASCAR problemを軽減できるかもしれない簡単な実装とさらなる妄想

f:id:ritou:20210819183905p:plain

こんばんは、ritouです。

いつもお友達同士でキャッキャウフフさせていただいているTwitterにてこんな投稿をしたところ、珍しくたくさんの反応をいただけました。

簡単に説明すると、

  • ソーシャルログインとかメアド/パスワードとかでログインできるサービスがある
  • 一度ログアウトして再びログイン画面を訪れると、「お前は前回Facebookでログインしたよな」って教えてくれた
  • どれ使ってたっけ...で迷わない!いい感じ〜

ってな話です。

どう実装されているか

  • ログインセッションそのものを扱うCookieとは別のCookieに認証方法を保存
  • ログイン画面ではその値を見て表示出しわけ

と言うようになっています。

f:id:ritou:20210819180525p:plain

ちなみにここを "tw" に変更すると、お前前回Twitter使ってたよな!?になります。

値の保存方法はCookieでもlocalStorageでも良いでしょう。 持ってる情報としてもそんなにセンシティブな扱いではなさそうです。

Googleのような細かく情報を残したログアウト状態を実装したわけではなく、単純に方法のみを残しておくだけでシンプルに実装可能です。 スマホ時代にあまりログアウトしないけどな〜って言うコメントもありましたが、それはそうですね。必ずしもみんなに需要があるわけではないでしょう。 しかし、PC向けのサービス、複数の認証方式を採用しているサービスは、あまり考えずにこれを使ってみても良いのではないでしょうか。

さらなる妄想

ここからは妄想です。

引用RTであったのが、「新しい端末でもこれできるといいな」ってやつです。

それに対して「その辺りはブラウザに頼みましょうや」と答えています。 この発言の背景としては、次のような展開を想像してのことです。

例えば

  • lastSignInMethod.set(method), lastSignInMethod.get() みたいなブラウザのAPIが定義される
    • ログイン成功時に lastSignInMethod.set("fb") を呼ぶ
    • ログイン画面で lastSignInMethod.get() を呼ぶ

みたいな標準化が行われたとします。 すると、「このサービスで前回何でログインしたか」を統一的な方法でブラウザに覚えてもらうことができるようになりますね。

少し別の話をしましょう。 この前、WebOTPの拡張についての記事を書きました。

ritou.hatenablog.com

この記事で紹介されている仕組みでは、ChromeではPCとAndroidで同じGoogleアカウントでログインすることでAndroidChromeにて受信したWebOTPをPCのChromeの方に送られます。 また、パスワードの同期の仕組みがあれば別環境の同一ブラウザで過去に利用したサービスを訪れた時に保存しておいたパスワードを自動入力することも可能でしょう。

こう言う仕組みを今回の「前回のログイン方法」にも当てはめるとどうなるでしょう。

  • PCであるサービスにFacebookでログインする
  • Androidのブラウザでそのサービスを開いてログイン画面に行った時に、「前回Facebookでログインしましたよね」みたいな表示になる

っていうのが実現できそうではないですか?ここまでできるとわりといろんな人が幸せになれそうです。

以上、単純な機能だとしても、いわゆるWeb標準的な仕組みにしてブラウザベンダーが頑張ってくれたら夢が広がりますね〜っていうお話でした。 こういう細けぇお話ができる場が少ないな〜って思いましたが、自分が勉強会をしていないからでした。申し訳ございません。

ではまた!