FedCMの現状把握
WebIDから名前が変わったFedCM(Federated Credential Management API)について、現状のステータスや実装を見ていきたいと思います。
FedCMの背景などについては以前OpenID TechNight vol.17でお話しした際の資料があるのでそちらもご参考いただけると助かります。
FedCMの参照先
FedCMの仕様や実装状況を追う上で参考にしたものは下記になります。
- https://github.com/WICG/FedCM
- https://wicg.github.io/FedCM/
-
https://github.com/chromium/chromium
- エラー発生時の原因調査、最新の実装状況把握のため
今回の確認内容
FedCMでは下記の3つのフローが存在しますが、まだDelegation oriented Flowは実装されていないようなため、Permission oriented FlowとMediated oriented Flowについて、正常系を動かすところまでやってみたいと思います。
- Permission oriented Flow
- Mediated oriented Flow
Delegation oriented Flow
動作確認環境
まずはこちらのドキュメントに従って、FedCMが動作する環境を準備します。
実際に動作を確認したChrome Canaryのバージョンは下記になります。
明日には仕様が変わっているかもしれませんので、現時点の実装状況であることをご了承ください。
macOS | Android |
---|---|
98.0.4758.2 | 99.0.4759.0 |
Chrome CanaryでFedCMを有効化
Chrome Canaryのアドレスバーでchrome://flags/#fedcm
と入力すると、FedCMの機能を有効化するフラグを立てることができます。
左側のボタンが「Default」となっているので、これを「Enabled」に変更します。
以上で、動作確認環境の準備は完了です。
次に実装をしていきます。
Permission oriented Flow
上記を見ながら、実装と処理の流れを追っていきます。
このフローはPCでしか動作確認できなかったのでご注意ください。
まずはRPからID Tokenのリクエストを行います。
navigator.credentials.get
のproviders
にはIdPの情報を、mode
はPermission oriented Flowを示すpermission
を指定します。
const token = await navigator.credentials.get({
federated: {
providers: [{
// IdPのURL
url: "https://idp.example.com",
// RPのClient ID
clientId: "client_id",
// nonce値
nonce: "456"
}],
// Permission oriented Flowの指定
mode: "permission"
}
});
ID Tokenのリクエストを行うと、ユーザに対して説明のためのプロンプトが表示されます。
少しわかりづらいですが、nebula-dynamic-lynx.glitch.me
はIdPのドメインで、artistic-roasted-sting.glitch.me
がRPのドメインです。
ここで、「Continue」を押すと、後続の処理が実行されます。
ブラウザがproviders
で指定したIdPの.well-known/webid
へリクエストをし、後続のフローで利用するエンドポイントを取得します。
ちなみにドキュメント上では.well-known/fedcm
となっていますが、現状の実装は.well-known/webid
へリクエストが来ているようでした。
{
"idp_endpoint": "https://idp.example/fedcm/idp_endpoint"
}
このあとブラウザはidp_endpoint
へリクエストを行い、ID Tokenの取得を試みます。
このidp_endpoint
はOAuthのAuthorization エンドポイントと互換性を保つように設計される想定となっているようで、scopeやclient_id、nonceなどのパラメータが付与されます。
現状はclient_idとnonceのみ指定した値が使われ、scopeはopenid profile email
が固定されています。
https://github.com/chromium/chromium/blob/016a05172b832baff78e418e51ab738257438c3c/content/browser/webid/federated_auth_request_impl.cc#L33-L47
IdPはidp_endpoint
へのリクエストに対して、ユーザーの状況に応じていずれかのレスポンスを返却します。
もし、ID Tokenを返却できる状況であれば、ID Tokenを返します。
これでRPに対してID Tokenが返却されID Tokenのリクエストが完結します。
{
"id_token": "eyJ..."
}
もし、ID Tokenを返却できない状況であれば、認証などを行うためのURLをブラウザに提供します。
{
"signin_url": "https://idp.example/fedcm/user_login"
}
ここで返却されたURLに対して、ブラウザが特別なUIと共にユーザーに明示的に同意を取得します。
最後にIdPはnavigator.id.provide()
呼び出すことでID TokenをRP返却することが可能となります。
navigator.id.provide("eyJ...");
以上がPermission oriented Flowの正常系の処理となります。
Mediated oriented Flow
上記を見ながら、実装と処理の流れを追っていきます。
このフローはAndroidでしか動作確認できなかったのでご注意ください。
まずはRPからID Tokenのリクエストを行います。
navigator.credentials.get
のproviders
にはIdPの情報を、mode
はMediated oriented Flowを示すmediated
を指定します。
デフォルト値もmediated
となっているため、指定しなければMediated oriented Flowとなります。
const token = await navigator.credentials.get({
federated: {
providers: [{
// IdPのURL
url: "https://idp.example.com",
// RPのClient ID
clientId: "client_id",
// nonce値
nonce: "456"
}],
// Mediated oriented Flowの指定
mode: "mediated"
}
});
ブラウザがproviders
で指定したIdPの.well-known/webid
へリクエストをし、後続のフローで利用するエンドポイントを取得します。
Mediated oriented Flowに関しても現状の実装は.well-known/webid
へリクエストが来ているようでした。
{
"idtoken_endpoint": "https://idp.example/fedcm/token_endpoint",
"accounts_endpoint": "https://idp.example/fedcm/accounts_endpoint",
"client_id_metadata_endpoint": "https://idp.example/fedcm/client_id_metadata_endpoint"
}
まずはaccounts_endpoint
へリクエストがされ、IdPのアカウント一覧をレスポンスで返却します。
{
"accounts": [{
"sub": "1234",
"name": "John Doe",
"given_name": "John",
"email": "john_doe@idp",
"picture": "https://idp.example/profile/123",
}, {
"sub": "5678",
"name": "Johnny",
"given_name": "Johnny",
"email": "johnny@idp",
"picture": "https://idp.example/profile/456"
}
]
}
レスポンスを返すことができると、ブラウザのUIとしてアカウントチューザーが表示されます。
スクリーンショットは取れなかったため、イメージは以下のようになります。
hallowed-tattered-split.glitch.me
はIdPのドメインで、artistic-roasted-sting.glitch.me
がRPのドメインです。
いずれかのアカウントを選択すると、下記のような画面が表示され、ID連携をするための同意を取得します。
この際に表示する「privacy policy」や「terms of service」のリンクはclient_id_metadata_endpoint
のレスポンスから取得しています。
{
"privacy_policy_url": "https://rp.example/privacy",
"terms_of_service_url": "https://rp.example/terms"
}
ここで「Continue as John」を押すと、idtoken_endpoint
へリクエストが発生します。
ドキュメント上では、下記のリクエストボディとなっていますが、
{
"account": 1234,
"request_id": "xyz123123zyx",
"request": {
"client_id": "myclientid",
"nonce": "abc987987cba"
}
}
現状は下記のようなリクエストボディとなっていました。
{
"request": "scope=openid profile email&client_id=client_id&nonce=456",
"sub": "1234"
}
いずれにせよ、Authorizationエンドポイントと互換性のあるパラメータと、選択した識別子が渡ってくると思われます。
IdPはidtoken_endpoint
では下記のようなレスポンスを返すことで、RPにID Tokenを返すことができます。
{
"id_token": "eyJ..."
}
以上がMediated oriented Flowの正常系の処理となります。
実装
一応動作確認のために実装した内容は下記となっているので、とりあえず動くものという感じですが、お好きに試してみたり、コードを見てもらえればと思います。
RP
- 動作ページ
- コード
IdP(Permission oriented)
IdP(Mediated oriented)
- 動作ページ
- コード
最後に
Permission oriented FlowとMediated oriented Flowに関しては、まだドキュメントと実装が乖離している箇所もありつつ、正常系の動作は動かせるような状況となっていました。
仕様と実装が乖離している場合、chromiumのコードを参照すると最新の実装状況を把握できるため、エラー文言などを検索するなどして実装を進められました。
また、Permission oriented FlowはPC、Mediated oriented FlowはAndroid端末で動作確認可能で、デモ実装の紹介も行ったので、ぜひ手元で試してみていただければと思います。
以上、引き続き余裕があれば記事を更新していこうと思うので、引き続きよろしくお願いいたします。
最後に、記事の投稿が遅れてしまい申し訳ありませんでした