FedCM によるアイデンティティプロバイダー連携
この記事では、アイデンティティプロバイダー (IdP) が、Federated Credential Management (FedCM) API と連携するために必要なすべての手順について詳しく説明します。
IdP 連携の手順
FedCM と連携するには、IdP は以下の手順を実行する必要があります。
- well-known ファイルを提供して IdP を識別する。
- アカウントの一覧表示およびアサーションの発行(およびオプションでクライアントのメタデータ)のために、構成ファイルとエンドポイントを提供する。
- ログイン状態 API を使用して、ログイン状態を更新する。
well-known ファイルを提供
潜在的なプライバシーの問題として、IdP が、明示的な同意なしに、ユーザーが認証依頼者 (RP) にアクセスしたかどうかを判別できるという問題が存在します。これはトラッキングに関連する課題であるため、IdP は自身の身元を確認し、この課題を軽減するために、well-known ファイルを提供することが要求されます。
この well-known ファイルは、認証情報を必要としない GET を通じてリクエストされますが、このリクエストはリダイレクトを追跡しないため、IdP がリクエストの発信者や、どの RP が接続を試みているかを知るのを防ぐことができます。
well-known ファイルは、IdP の 登録可能なドメイン の /.well-known/web-identity から提供しなければなりません。例えば、IdP のエンドポイントが https://accounts.idp.example/ で提供されている場合、https://idp.example/.well-known/web-identity で well-known ファイルを提供する必要があります。well-known ファイルのコンテンツは、以下の JSON 構造です。
{
"provider_urls": ["https://accounts.idp.example/config.json"]
}
provider_urls メンバーには、RP が IdP と対話するために使用できる、有効な IdP 設定ファイルを指す URL の配列が含まれている必要があります。現在、この配列の長さは 1 に制限されています。
HTTP の Sec-Fetch-Dest ヘッダー
FedCM を介してブラウザーから送信されるすべてのリクエストには、 ヘッダーが記載されます。資格情報リクエストを受信するすべての IdP エンドポイント(つまり、Sec-Fetch-Dest: webidentityaccounts_endpoint および id_assertion_endpoint)は、CSRF 攻撃から保護するために、このヘッダーが含まれていることを確認する必要があります。
構成ファイルとエンドポイントの提供
IdPの構成ファイルには、ブラウザーがアイデンティティ連係フローを処理し、ログインを管理するために必要なエンドポイントの一覧が提供されます。これらのエンドポイントは、構成ファイルと同じオリジンである必要があります。
ブラウザーは、GET メソッドを使用して、資格情報なしで構成ファイルへのリクエストを行います。このリクエストはリダイレクトを追跡しません。これにより、IdP がリクエストの送信者や、接続を試みているRPを把握するのを効果的に防ぐことができます。
設定ファイル(この例では https://accounts.idp.example/config.json にホストされています)は、以下の JSON 構造である必要があります。
{
"accounts_endpoint": "/accounts.php",
"account_label": "developer",
"supports_use_other_account": true,
"client_metadata_endpoint": "/client_metadata.php",
"disconnect_endpoint": "/disconnect.php",
"id_assertion_endpoint": "/assertion.php",
"login_url": "/login",
"branding": {
"background_color": "green",
"color": "0xFFEEAA",
"icons": [
{
"url": "https://idp.example/icon.ico",
"size": 25
}
]
}
}
プロパティは以下の通りです。
accounts_endpoint-
アカウント一覧エンドポイントの URL。このエンドポイントからは、ユーザーが IdP に現在ログインしているアカウントの一覧が返されます。ブラウザーはこれを使用して、ブラウザーが提供する FedCM UI 上でユーザーに表示させるログイン選択肢の一覧を作成します。
account_label省略可-
この文字列を指定すると、この IdP が連合認証に使用される際に、返されるべきアカウントのサブセットの識別子が指定されます。
get()リクエストが行われた場合、アカウントエンドポイントからは、label_hints引数がこの文字列と一致するアカウントのみが返されます。 supports_use_other_account省略可-
論理値で、デフォルトは
falseです。trueに設定すると、ユーザーは現在ログインしているアカウントとは異なるアカウントでログイン可能になります(IdP が複数のアカウントに対応している場合)。これは、アクティブモード を指定したget()呼び出しにのみ適用されます。メモ: ブラウザーのログイン画面では、このボタンは同様に「別のアカウントを選択」といったボタンとして表示されるでしょう。
client_metadata_endpoint省略可-
FedCM UI で使用する、クライアントメタデータエンドポイントの URL。このエンドポイントは、RP のメタデータおよび利用規約ページへの URL を指定します。
disconnect_endpoint省略可-
RP が
IdentityCredential.disconnect()メソッドを介して IdP からの接続を切断するために使用する、切断エンドポイントの URL。 id_assertion_endpoint-
ID アサーションエンドポイントの URL。ここに有効なユーザー資格情報を送信すると、RP が認証の検証に使用することができる検証トークンが返されます。
login_url-
ユーザーが IdP にログインするためのログインページの URL。
branding省略可-
IdP の希望に応じて外観をカスタマイズするために、ブラウザーが提供する FedCM UI で使用されるブランド情報を記述します。指定されたアイコンのサイズは、パッシブモードでは
25(25px) 以上、アクティブモードでは40(40px) 以上でなければなりません(詳細については、アクティブモードとパッシブモードを参照してください)。
以下の表は、FedCM API によって行われるさまざまなリクエストをまとめたものです。
| エンドポイント/リソース | メソッド | 資格情報(クッキー付き) | Origin を含む |
|---|---|---|---|
well-known/config.json |
GET |
なし | なし |
accounts_endpoint |
GET |
あり | なし |
client_metadata_endpoint |
GET |
なし | あり |
disconnect_endpoint |
POST |
あり | あり |
id_assertion_endpoint |
POST |
あり | あり |
メモ: これらのエンドポイントが呼び出される FedCM フローの詳細については、FedCM ログインフローを参照してください。
メモ: FedCM API がここで説明するエンドポイントに対して行うリクエストは、プライバシー保護の観点から、リダイレクトを追跡することは許可されていません。
アカウントリストのエンドポイント
ブラウザーは、GETメソッドを使用してこのエンドポイントにリクエストを送信します。このリクエストには、client_id 引数、Origin ヘッダー、Referer ヘッダーが含まれていません。これにより、IdP がユーザーがどの RP にログインしようとしているかを把握できないようにしています。
例を示します。
GET /accounts.php HTTP/1.1
Host: idp.example
Accept: application/json
Cookie: 0x23223
Sec-Fetch-Dest: webidentity
このリクエストには資格情報が含まれています。つまり、IdPのサイト用のクッキーが含まれており、IdPはこれを使用して、ユーザーがどのIdPアカウントにログインしたかを確認することができます。
なお、このエンドポイントに対するブラウザーのリクエストはサイト間リクエストであるため、クッキーには SameSite 属性の値が None であるもののみが含まれます。つまり、IdP はクロスサイトリクエストフォージェリー (CSRF) 攻撃に対する防御策の一環として SameSite を使用できないため、代替の防御策を実装しなければなりません。
このレスポンスには、ユーザーが現在ログインしているすべての IdP アカウントのリスト(特定の RP に限定されない)が、以下の JSON 構造で返されます。
{
"accounts": [
{
"id": "elaina_maduro",
"given_name": "Elaina",
"name": "Elaina Maduro",
"email": "elaina_maduro@idp.example",
"tel": "+491234567890",
"username": "elaina420",
"picture": "https://idp.example/profile/123",
"approved_clients": ["123", "456", "789"],
"domain_hints": ["rp1.example.com", "rp3.example.com"],
"label_hints": ["developer", "admin"],
"login_hints": ["elaina_maduro", "elaina_maduro@idp.example"]
},
{
"id": "elly",
"given_name": "Elly",
"username": "elly123",
"email": "Elly@idp.example",
"picture": "https://idp.example/profile/456",
"approved_clients": ["abc", "def", "ghi"],
"domain_hints": ["rp1.example.com", "rp2.example.com"],
"label_hints": ["developer", "test"],
"login_hints": ["elly", "elly@idp.example"]
}
]
}
これには以下の情報が記載されます。ここで、name、email、username、tel は任意ですが、少なくとも 1 つは存在する必要があり、かつ空であってはなりません。
id-
ユーザーの固有の ID。
name省略可-
ユーザーの苗字。
email省略可-
ユーザーのメールアドレス。
tel省略可-
ユーザーの電話番号。任意の書式で指定可能です。
username省略可-
そのユーザーのユーザー名。
given_name省略可-
ユーザーの下の名前。
picture省略可-
ユーザーのアバター画像の URL。
approved_clients省略可-
ユーザーが持つ RP クライアントの配列。
domain_hints省略可-
そのアカウントに関連付けられたドメインの配列。RP は、
domainHintプロパティを含むget()呼び出しを行うことで、返されるアカウントをドメインごとに絞り込むことができます。 label_hints省略可-
そのアカウントを識別するアカウントの種類のラベルを指定する文字列の配列です。設定ファイルで
account_labelが指定されている場合、アカウント一覧エンドポイントからは、label_hintsにそのラベルが含まれているアカウントのみが返されます。 login_hints省略可-
アカウントを表す文字列の配列です。これらの文字列は、ブラウザーがユーザーに提示するログイン用アカウントオプションの一覧を絞り込むために使用されます。これは、関連する
get()呼び出しにおいて、identity.providers内にloginHintプロパティが指定された場合に実行されます。login_hints配列内の文字列が、指定されたloginHintと一致するアカウントはすべて含まれます。
メモ: ユーザーがどの IdP アカウントにもログインしていない場合、エンドポイントは HTTP 401 (Unauthorized) を返す必要があります。
クライアントメタデータのエンドポイント
ブラウザーは、GETメソッドを使用して、このエンドポイントに認証情報なしのリクエストを送信します。この際、get()呼び出しの引数としてclientIdが渡されます。
例を示します。
GET /client_metadata.php?client_id=1234 HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Accept: application/json
Sec-Fetch-Dest: webidentity
リクエストが成功した場合のレスポンスには、RP のメタデータおよび利用規約のページへの URL が含まれます。これらは、ブラウザーが提供する FedCM UI で使用されます。これは JSON 構造に従う必要があります。
{
"privacy_policy_url": "https://rp.example/privacy_policy.html",
"terms_of_service_url": "https://rp.example/terms_of_service.html"
}
disconnect エンドポイント
IdentityCredential.disconnect() を呼び出すと、ブラウザーは、オリジンをまたぐ POST リクエストを、クッキーを含め、Content-Type を application/x-www-form-urlencoded にして、以下の情報を含めて切断エンドポイントに送信します。
account_hint-
IdP が切断対象のアカウントを特定するために使用するアカウントヒントを指定する文字列。
client_id-
RP のクライアント識別子を指定する文字列。
例を示します。
POST /disconnect HTTP/1.1
Host: idp.example
Origin: rp.example
Content-Type: application/x-www-form-urlencoded
Cookie: 0x123
Sec-Fetch-Dest: webidentity
account_hint=account456&client_id=rp123
リクエストを受信すると、IdP サーバーは以下の処理を行う必要があります。
-
このリクエストに対しては、CORS (オリジン間リソース共有)で応答します。
-
リクエストに HTTP の
Sec-Fetch-Destヘッダーが含まれており、ディレクティブがwebidentityであることを確認します。 -
Originヘッダーを、client_idによって特定された RP オリジンと照合します。一致しない場合は、プロミスを拒否します。 -
account_hintに一致するアカウントを探します。 -
RP に接続されているアカウントの一覧から、そのユーザーアカウントの接続を解除します。
-
識別されたユーザーの
account_idを JSON 書式で返答します。json{ "account_id": "account456" }
メモ:
IdP が RP に関連付けられているすべてのアカウントの接続を解除したい場合は、どの account_id とも一致しない文字列(例えば "account_id": "*")を渡すことができます。
ID アサーションエンドポイント
ブラウザーは、POST メソッドを使用し、コンテンツタイプを application/x-www-form-urlencoded として、このエンドポイントに資格情報を含むリクエストを送信します。このリクエストには、ログインの試行に関する詳細や、検証対象のアカウントに関する内容の本体も含まれます。
こんな感じになるはずです。
POST /assertion.php HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Content-Type: application/x-www-form-urlencoded
Cookie: 0x23223
Sec-Fetch-Dest: webidentity
account_id=123&client_id=client1234&disclosure_text_shown=true&is_auto_selected=true
このエンドポイントへのリクエストは、ユーザーが関連するブラウザーの UI からログインに使用できるアカウントを選択した結果として送信されます。有効なユーザー資格情報が送信されると、このエンドポイントは、アイデンティティ連携で使用している IdP が定める利用概要に従い、RP が自社のサーバー上でユーザーを検証するために使用できる検証トークンを応答として返す必要があります。RP がユーザーの検証を完了すると、そのユーザーをログインさせたり、サービスに登録させたりすることができます。
{
"token": "***********"
}
リクエストの本体には、以下の引数が含まれています。
client_id-
RP のクライアント識別子(元の
get()リクエストのclientIdと一致する)。 account_id-
ログイン対象のユーザーアカウントの固有の ID(アカウント一覧エンドポイントのレスポンスに含まれるユーザーの
idと一致するもの)。 params省略可-
元の
get()リクエストからのparamsオブジェクトのシリアライズ結果。 disclosure_text_shown-
開示テキストが表示されたかどうかを示す、
"true"または"false"の文字列。開示テキストとは、ユーザーが IdP にログインしているものの、現在の RP に特定のアカウントを保有していない場合にユーザーに表示される情報(利用規約やプライバシーポリシーへのリンクを含めることができる場合もある)のことです(この場合、ユーザーは IdP の ID を使用して「…として続ける」を選択し、RP 上で対応するアカウントを生成する必要があります)。 is_auto_selected-
"true"または"false"の文字列で、自動再認証、すなわちユーザーの介入なしに、認証検証リクエストが発行されたかどうかを示します。これは、get()の呼び出しにおいて、mediationオプションの値が"optional"または"silent"に設定されている場合に発生する可能性があります。IdP にとって、パフォーマンス評価や、より高いセキュリティが求められる場合に備えて、自動再認証が行われたかどうかを把握しておくことは有用です。例えば、IdP は、明示的なユーザーの介入が要求されることを RP に指示するエラーコード(mediation="required")を返すことができます。
メモ:
get() の呼び出しが成功した場合、is_auto_selected の値も IdentityCredential.isAutoSelected プロパティを介して RP に同時に通知されます。
ID アサーションエンドポイントの CORS ヘッダー
ID アサーションエンドポイントのレスポンスには、Access-Control-Allow-Origin および Access-Control-Allow-Credentials ヘッダーを含める必要があり、Access-Control-Allow-Origin にはリクエスト元のオリジンを記載する必要があります。
Access-Control-Allow-Origin: https://rp.example
Access-Control-Allow-Credentials: true
なお、Access-Control-Allow-Origin はリクエスト元 (RP) の特定のオリジンに設定する必要があり、ワイルドカード値 * を指定することはできません。
これらのヘッダーがない場合、リクエストはネットワークエラーにより失敗します。
ID アサーションエラーのレスポンス
IdP がトークンを発行できない場合(たとえば、クライアントが認証されていない場合など)、ID アサーションエンドポイントは、エラーの詳細を含むエラーレスポンスを返します。例を示します。
{
"error": {
"code": "access_denied",
"url": "https://idp.example/error?type=access_denied"
}
}
エラーレスポンスのフィールドは次の通りです。
code省略可-
文字列。OAuth 2.0 で指定されたエラー一覧 に掲載されている既知のエラー、または任意の文字列のどちらかです。
url省略可-
URL。これは、エラーの修正方法やカスタマーサービスへの連絡先など、ユーザーに表示させるエラーに関する、読み取り可能な情報が記載されたウェブページである必要があります。この URL は、IdP の設定 URL と同じサイト内にあるものでなければなりません。
この情報は、いくつかの異なる方法で使用できます。
- ブラウザーは、ユーザーに対して何が問題だったかを伝える独自のUIを表示させることができます(例えば、Chrome のドキュメントを参照してください)。なお、IdP サーバーが利用できないためにリクエストが失敗した場合、当然ながら IdP サーバーから情報を返すことはできません。そのような場合、ブラウザーは一般的なメッセージでその旨を通知します。
- ログインを試行するために使用された関連付けられた RP
navigator.credentials.get()の呼び出しは、エラー情報を含むIdentityCredentialErrorを返してそのプロミスを拒否します。RP はこのエラーをキャッチし、ブラウザーの独自の UI に続いて、ユーザーが次回のログインに成功できるよう役立つ情報を提供することができます。
ログイン状態 API を使用してログイン状態を更新する
ログイン状態 API を使用すると、IdP は特定のブラウザーにおける自身のログイン(サインイン)状態をそのブラウザーに通知することができます。ここでいう「ログイン状態」とは、「現在のブラウザーで、IdP にユーザーがログインしているかどうか」を指します。ブラウザーはこの状態を IdP ごとに保存します。FedCM API はこれを利用して、IdP へのリクエスト数を削減します(IdP にログインしているユーザーがいなければ、アカウント情報を取得するためのリクエストに時間を費やす必要がないためです)。また、これにより潜在的なタイミング攻撃のリスクも軽減されます。
既知のそれぞれの IdP(その設定 URL によって識別される)について、ブラウザーはログイン状態を表す 3 値変数を保持しており、その値は以下の 3 つのいずれかになります。
"logged-in": IdP には、少なくとも 1 つのユーザーアカウントがログインしています。この段階では、RP やブラウザーはそれがどのユーザーであるかを知らないことに注意してください。特定のユーザーに関する情報は、FedCM フローの後の段階で、IdP のaccounts_endpointから返されます。"logged-out": 現在、すべての IdP アカウントからログアウトされています。"unknown": この IdP のログイン状態は不明です。これがデフォルト値です。
ログイン状態の設定
ユーザーが IdP にログインまたはログアウトした際、IdP はそのログイン状態を更新する必要があります。これには 2 つの方法があります。
-
HTTP の
Set-Loginレスポンスヘッダーを、最上位のナビゲーションまたは同一オリジンのサブリソースへのリクエストで設定することができます。httpSet-Login: logged-in Set-Login: logged-out -
Navigator.login.setStatus()メソッドは、IdP のオリジンから呼び出すことが可能です。js/* ログイン状態を設定 */ navigator.login.setStatus("logged-in"); /* ログアウト状態を設定 */ navigator.login.setStatus("logged-out");
ログイン状態がフェデレーションログインのフローに与える影響
RP がフェデレーションログインを試行する際、ログイン状態を調べます。
- IdP のログイン状態が
"logged-in"の場合、アカウント一覧エンドポイントに対してリクエストが行われ、ログインに利用できるアカウントが、ブラウザーが指定する FedCM ダイアログにユーザーに表示されます。 - すべての IdP のログイン状態が
"logged-out"の場合、FedCM のget()リクエストによって返されるプロミスは、アカウント一覧エンドポイントへのリクエストを行わずに拒否されます。このような場合、開発者は、例えばユーザーに適切な IdP にアクセスしてログインするよう促すなど、フローを適切に処理する必要があります。 - IdP のログイン状態が
"unknown"の場合、アカウント一覧エンドポイントに対してリクエストが行われ、そのレスポンスに応じてログイン状態が更新されます。- エンドポイントが利用できるアカウントのリストを返した場合は、状態を
"logged-in"に更新し、ブラウザーが指定する FedCM ダイアログでユーザーにログインオプションを表示させます。 - エンドポイントがアカウントを返さない場合は、状態を
"logged-out"に更新します。利用できるlogged-in状態の IdP が他にない場合、FedCM のget()リクエストによって返されるプロミスは拒否されます。
- エンドポイントが利用できるアカウントのリストを返した場合は、状態を
ブラウザーと IdP のログイン状態がずれてしまった場合はどうなるのか
ログイン状態 API は IdP のログイン状態をブラウザーに通知しますが、ブラウザーと IdP の状態が同期しなくなる可能性があります。例えば、IdP のセッションが期限切れになると、すべてのユーザーアカウントがログアウトされた状態になるにもかかわらず、ログイン状態は "logged-in" のままになる場合があります(アプリケーションがログイン状態を "logged-out" に設定できなかったため)。このような場合、フェデレーションログインを試みると、IdP のアカウント一覧エンドポイントへのリクエストが行われますが、セッションが利用できなくなったため、利用できるアカウントは返されません。
この場合、ブラウザーはダイアログで IdP のログインページを開くことで、ユーザーが IdP に動的にログインできるようにします(ログイン URL は、IdP の設定ファイル内の login_url で探すことができます)。このフローの具体的な動作はブラウザーに委ねられています。例えば、Chrome では同様に処理されます。
ユーザーが IdP にログインすると、IdP は以下の処理を行う必要があります。
- ログイン状態の設定を
"logged-in"に設定することで、ユーザーがログインしたことをブラウザーに通知します。 IdentityProvider.close()メソッドを呼んで、ログインダイアログを閉じます。
関連情報
- Federated Credential Management API - developer.chrome.com (2023)