図解 OpenID Connect による ID 連携

# ID 連携フローの説明

## 1

ウェブブラウザを使い、ウェブサービスのログインページにアクセスします。

![oidc-id-federation_ja_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/65c4ac54-f0e3-bc62-0de6-1dfbcf8f8cb5.png)

## 2

ウェブサービスはログインページを生成し、ウェブブラウザに返します。ウェブサービスが外部の**アイデンティティプロバイダ**（**IdP**）との **ID 連携**をサポートしていれば、ログインページ内に ID 連携を開始するためのリンクが埋め込まれます。

![oidc-id-federation_ja_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/485855fa-548a-fef6-5786-0183337e4982.png)

## 3

ID 連携を開始するためのリンクをクリックします。

![oidc-id-federation_ja_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/0eb97d07-5330-27c2-9cb5-3756449bd8eb.png)

## 4

ID 連携開始の要望を受けたウェブサービスは、対象となる IdP への**認証リクエスト**（[OpenID Connect Core 1.0 Section 3.1.2.1. Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)）を作成します。

認証リクエストの形式は、リクエストパラメーター群を含めた、IdP の**認可エンドポイント**（[RFC 6749 Section 3.1. Authorization Endpoint](https://www.rfc-editor.org/rfc/rfc6749.html#section-3.1)）を指す URL です。ですので、認証リクエストを作成するにあたり、認可エンドポイントの URL を知る必要があります。IdP の技術文書を読めば認可エンドポイントの URL を知ることはできますが、もしも IdP が [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) をサポートしているなら、**ディスカバリーエンドポイント**が返す IdP 設定情報の中から認可エンドポイントの URL を取り出すこともできます。

![oidc-id-federation_ja_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/e179e04c-444f-8138-86fa-f516bca12529.png)

なお、仕様により（[OpenID Connect Discovery 1.0 Section 4. Obtaining OpenID Provider Configuration Information](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig)）、ディスカバリーエンドポイントの場所は「<code><i>{IdPの識別子}</i>/.well-known/openid-configuration</code>」と決められているので、IdP の識別子さえ知っていればディスカバリーエンドポイントの場所を特定することができます。

:::note info
認証リクエストに含める <code>state</code> リクエストパラメーターの値はこのタイミングで生成し、HTTP セッションの中に入れて覚えておきます。また、<code>code_challenge</code> リクエストパラメーター（<a href="https://www.rfc-editor.org/rfc/rfc7636.html">RFC 7636</a>）を含めるなら、code verifier の生成もこのタイミングになります。
:::

## 5

認証リクエスト作成後、ウェブブラウザを IdP の認可エンドポイントにリダイレクトするため、ウェブサービスは HTTP ステータスコード `302 Found`（リダイレクトを促すものであれば他のステータスコードでもかまわない）を返します。このとき、`Location` レスポンスヘッダーに認証リクエストを表す URL をセットしておきます。

```http
HTTP/1.1 302 Found
Location: 認可エンドポイント?パラメーター群...
```

![oidc-id-federation_ja_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/476e9575-e14f-cfe2-d692-b09e48b0984f.png)

## 6

HTTP ステータスコード `302 Found` を受け取ったウェブブラウザは、`Location` ヘッダーに書かれている URL にアクセスします。結果として、ウェブブラウザは、IdP の認可エンドポイントに認証リクエストを投げることになります。

![oidc-id-federation_ja_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/1ef41a76-c2a5-dd89-e657-9ca60297d446.png)

## 7

認証リクエストを受け取った認可エンドポイントは、（１）ユーザーを認証して（２）ウェブサービスへ権限を渡すことに対する同意をユーザーから得るため、**認可ページ**を作成し、ウェブブラウザに返します。

![oidc-id-federation_ja_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/9ffd0f59-dabb-1393-3ce1-4c2a99594fd5.png)

図では、ユーザー認証と同意取得をまとめておこなうためのページが返されていますが、ページ遷移を複数回伴う実装でもかまいません。特に近年は、ユーザーを**特定**した後に**認証**手続きを開始する（identify してから authenticate する）のが好ましいとされているので、むしろ、ページ遷移もしくは JavaScript による表示切り替えにより遷移を複数回おこなう実装のほうが主流かもしれません。

## 8

ユーザーは、ユーザー認証に必要な情報を入力し、ウェブサービスからのリクエストを承認します。

![oidc-id-federation_ja_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/fce57685-f36b-32fc-4aa6-bad5aee33091.png)

どのようにユーザーを認証するかは IdP 次第です。図では ID とパスワードを要求していますが、近年のユーザー認証方法は多種多様です。

## 9

ユーザー認証と同意確認後、IdP は<b>認可コード</b>を生成します。

:::note info
ここでは、先の認証リクエストのフローが OpenID Connect <b>認可コードフロー</b>（<code>response_type</code> が <code>code</code> で <code>scope</code> に <code>openid</code> を含む）だったと想定しています。このフローの図解は『<a href="https://qiita.com/TakahikoKawasaki/items/4ee9b55db9f7ef352b47">OpenID Connect 全フロー解説</a>』の「<a href="https://qiita.com/TakahikoKawasaki/items/4ee9b55db9f7ef352b47#1-response_typecode">1. response_type=code</a>」にあります。
:::

そして、認可コードをウェブサービスの**リダイレクションエンドポイント**（[RFC 6749 Section 3.1.2. Redirection Endpoint](https://www.rfc-editor.org/rfc/rfc6749.html#section-3.1.2)）に届けるため、ウェブサービスが IdP に事前に登録しておいた**リダイレクション URI** にパラメーターとして認可コードを付加し、URL を作成します。

:::note info
IdP は、認証リクエストに含まれていたものと同じ値をもつ <code>state</code> パラメーターもリダイレクション URI に付加します。また、IdP が <a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-iss-auth-resp/">OAuth 2.0 Authorization Server Issuer Identification</a> をサポートしているなら、<code>iss</code> パラメーターも付加します。
:::

そのように作成した URL にウェブブラウザをリダイレクトするため、IdP はウェブブラウザに HTTP ステータスコード `302 Found`（リダイレクトを促すものであれば他のステータスコードでもかまわない）を返します。このとき、`Location` レスポンスヘッダーに生成した URL をセットしておきます。

```http
HTTP/1.1 302 Found
Location: リダイレクションエンドポイント?code=認可コード&state=...&iss=...
```

![oidc-id-federation_ja_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/a213340b-2a55-bfa3-57d1-2c8f4ab0859f.png)

このフローの技術詳細については『[OAuth 2.0 の認可レスポンスとリダイレクトに関する説明](https://qiita.com/TakahikoKawasaki/items/8567c80528da43c7e844)』を参照してください。

## 10

HTTP ステータスコード `302 Found` を受け取ったウェブブラウザは、`Location` ヘッダーに書かれている URL にアクセスします。結果として、認可コードがウェブサービスに渡ってきます。

![oidc-id-federation_ja_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/c3783e12-814e-7c90-4cf8-bc066d5f4f72.png)

:::note warn
認可コードに加えて <code>state</code> パラメーターも渡ってきます。リダイレクションエンドポイントの実装は、HTTP セッションから <code>state</code> の値を取り出し、その値がレスポンスに含まれる <code>state</code> パラメーターの値と一致することを確認します。これは CSRF 対策です。詳細は <a href="https://www.rfc-editor.org/rfc/rfc6749.html#section-10.12">RFC 6749 Section 10.12. Cross-Site Request Forgery</a> を参照してください。
:::

:::note warn
IdP の <code>authorization_response_iss_parameter_supported</code> メタデータの値が <code>true</code> であれば、リダイレクションエンドポイントの実装は、レスポンスに <code>iss</code> パラメーターが含まれていること及びその値が IdP の識別子と一致することを確認します。これは Mix-up 攻撃対策です。詳細は <a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-iss-auth-resp/">OAuth 2.0 Authorization Server Issuer Identification</a> を参照してください。
:::

## 11

ウェブサービスは、受け取った認可コードを用いて、IdP の**トークンエンドポイント**（[RFC 6749 Section 3.2. Token Endpoint](https://www.rfc-editor.org/rfc/rfc6749.html#section-3.2)）に**トークンリクエスト**を投げます。

![oidc-id-federation_ja_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/c23bcf20-76ae-06bc-3be1-57af6527c0ec.png)

## 12

トークンエンドポイントは**アクセストークン**と **ID トークン**（[OpenID Connect Core 1.0 Section 2. ID Token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken)）を生成して返します。

![oidc-id-federation_ja_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/310ff1f1-fc27-9d38-4c5c-7722c82b79da.png)

## 13

ウェブサービスは受け取った ID トークンの検証をおこないます。検証項目は多岐に渡りますが（[OpenID Connect Core 1.0 Section 3.1.3.7. ID Token Validation](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation)）、最も重要なのは ID トークンの署名を検証する作業です。

署名検証のためには、署名検証のための鍵を入手する必要があります。その鍵は、署名アルゴリズムが対称鍵系であればクライアントシークレット、非対称鍵系であれば公開鍵です（[OpenID Connect Core 1.0 Section 10.1. Signing](https://openid.net/specs/openid-connect-core-1_0.html#Signing)）。

公開鍵の場合、IdP がどこかで公開しているはずです。一般的には、**JWK Set エンドポイント**で公開しています。JWK Set エンドポイントの場所に関する情報は、ディスカバリーエンドポイントから返される IdP 設定情報の中に書かれています。

![oidc-id-federation_ja_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/9afd1240-706c-6987-207d-80c82a68fd22.png)

ID トークンの検証が済めば、「IdP によりユーザーが認証された」ことを確認できたことになります。

## 14

ID トークンのペイロード部に含まれる情報だけで十分なのであれば、IdP とやりとりする必要はこれ以上ありませんが、ユーザーに関する情報をもっと欲しい場合は、IdP の**ユーザー情報エンドポイント**（[OpenID Connect Core 1.0 Section 5.3. UserInfo Endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo)）にアクセスします。ユーザー情報エンドポイントにアクセスする際、トークンエンドポイントから発行されたアクセストークンが必要になります。

![oidc-id-federation_ja_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/11c32bab-caf0-b109-fe24-94d03ba45072.png)

:::note warn
あまり知られていませんが、アクセストークンの発行を伴うフローで ID トークンが発行された場合、特殊なスコープ（<a href="https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims">OpenID Connect Core 1.0 Section 5.4. Requesting Claims using Scope Values</a>）で要求されたクレーム群は ID トークンには含まれません。そのため、ID トークンに含まれるユーザー属性情報は、期待されているものより少ないことがほとんどです。告白すると、この仕様に気付いたのは Authlete 実装後かなり経ってからです。<a href="https://authlete.github.io/authlete-java-common/com/authlete/common/dto/Service.html">Service</a> クラスの <a href="https://authlete.github.io/authlete-java-common/com/authlete/common/dto/Service.html#isClaimShortcutRestrictive--">isClaimShortcutRestrictive()</a> の説明も参照してください。
:::

## 15

ユーザー情報エンドポイントはユーザーに関する情報を返します。

![oidc-id-federation_ja_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/54979e3c-0fcb-b409-2bb5-d3a8dbcab5c0.png)

ユーザー情報を受け取った後にその情報をどう扱うかはウェブサービスの実装次第です。例えば、その情報をもとにウェブサービス上にユーザーアカウントを新規作成し、そのアカウント用のデータレコードに、ユーザー情報エンドポイントから取得した情報をコピーするかもしれません。または、データコピーは最低限にとどめ、IdP のユーザー情報エンドポイントから最新のユーザー情報を好きなときに取得できるよう、かわりにアクセストークンを覚えておくという実装にするかもしれません。

## 16

ウェブサービスは、ユーザーをログイン状態にし、ウェブサービスのトップページ（でなくてもよいがどこかのページに）ウェブブラウザを遷移させます。

![oidc-id-federation_ja_16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/028e393f-bb98-6139-407e-6f0d3d2db3cd.png)

## 17

ウェブブラウザはトップページにアクセスします。

![oidc-id-federation_ja_17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/8062ff4f-d1c2-67d9-c1ac-1d3e1a0c59a2.png)

## 18

ウェブサービスはトップページを返します。このとき、ユーザーはログイン状態と認識されています。

![oidc-id-federation_ja_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/27e993b3-04f6-339d-101f-13cc1bf53b62.png)

## 19

以上で ID 連携完了です。

![oidc-id-federation_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/e03e05bc-1555-d8a3-9cdc-3ed564aa2ca2.png)

# ID 連携のチュートリアル

[Authlete 社](https://www.authlete.com/ja/)のウェブサイト上で公開されている『[外部 IdP との連携](https://www.authlete.com/ja/developers/tutorial/idp/)』という技術文書では、IdP として [Okta](https://www.okta.com/)、ウェブサービスとして <a href="https://github.com/authlete/java-oauth-server">java-oauth-server</a>（認可サーバーのサンプル実装）を用いた ID 連携を紹介しています。ウェブサービスとして認可サーバーを用いているため、フローがかなり複雑になっていますが（認可サーバーの認可ページから ID 連携する構成）、ID 連携部分は本記事で紹介したものと同一です。

![id-federation_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/ecce193a-c068-fb5f-91f0-7598193d51fc.png)


実際に動かすことができますので、是非チュートリアルを試してみてください。また、ID 連携の実装詳細にご興味があれば、java-oauth-server の [Federation.java](https://github.com/authlete/java-oauth-server/blob/master/src/main/java/com/authlete/jaxrs/server/federation/Federation.java) と [FederationEndpoint.java](https://github.com/authlete/java-oauth-server/blob/master/src/main/java/com/authlete/jaxrs/server/api/FederationEndpoint.java) を中心にコードを読んでみてください。

:::note warn
試していませんが、某有名 IDaaS のトークンエンドポイントはカスタムリクエストパラメーターを必須で要求するので、java-oauth-server に書かれている汎用コードだと ID 連携に失敗すると思われます。
:::