92 views
# (OpenID Connect Core 1.0) 15.5.Implementation Notes - 15.7. Related Specifications and Implementer's Guides ###### tags: `oauth2/oidc` 担当::@kyosu: [toc] ## 15.5. Implementation Notes ### 15.5.1. Authorization Code Implementation Notes 認可コードまたはハイブリッドフローを使用する場合、認可コードを使用したトークンリクエストへの応答として、トークンエンドポイントからIDトークンが返される。 一部の実装では、返されるIDトークンに関する状態を認可コード値にエンコードする場合がある。他の場合、認可コード値をこの状態を格納するデータベース内のインデックスとして使用する場合がある。 :::info 認可コード → IDトークン に交換するために、どこにIDTokenに関する情報を保持しておくかという話。 1. ID Tokenについての状態を認可コードの値にエンコード 2. 認可コードの値をデータベースのインデックスとして使用 の二通りが考えられる。一般的には(GitHub上で公開されているOPの実装などを見ても)後者の実装方針の方が多いと思われる。 なお、この話からも分かる通り認可コードの具体的なフォーマットは仕様上(OAuth2.0/OIDCにおいて)定められていない。(十分なエントロピーを持っていればよいはず) ::: :::info 余談:認可コードが1のような場合にJWTが使われることがあるかなと思い調べてたら出てきたもの。これは認可コードがJWTというわけではないので別物だが、紹介しておく。 認可グラントをJWTにした、JWT認可グラントというフローも [RFC7523](https://datatracker.ietf.org/doc/html/rfc7523)で定義されている。 リクエスト例(トークンエンドポイント)。 granttypeに`urn:ietf:params:oauth:grant-type:jwt-beare`、assertionにJWTを入れる ``` POST /token.oauth2 HTTP/1.1 Host: as.example.com Content-Type: application/x-www-form-urlencoded grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer &assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0. eyJpc3Mi[...omitted for brevity...]. J9l-ZhwP[...omitted for brevity...] ``` トークンレスポンス ```json { "access_token":"NEdL-q9EfOI4S5XzaMeimXAXVqS139Jm9DTYeLUAd5o", "token_type":"Bearer", "expires_in":86400, "scope":"email" } ``` 参考: - https://qiita.com/TakahikoKawasaki/items/23dcf6e99f8a03b6614d - https://www.authlete.com/ja/developers/jwt_authorization_grant/ user-agent のやり取りを必要としないので、サーバー間通信での取得かつ、個別に定義できるので、ユースケースはありそう ::: ### 15.5.2. Nonce Implementation Notes nonceパラメーターの値は、セッションごとの状態を含み、攻撃者に予測不可能である必要がある。Webサーバークライアントに対してこれを達成する1つの方法は、HttpOnlyセッションCookieとして暗号学的にランダムな値を保存し、その値の暗号ハッシュをnonceパラメーターとして使用すること。その場合、返されるIDトークンのnonceは、第三者によるIDトークンの再生を検出するためにセッションCookieのハッシュと比較される。JavaScriptクライアントや他のブラウザベースのクライアントに適用可能な関連する方法は、HTML5ローカルストレージに暗号学的にランダムな値を保存し、この値の暗号ハッシュを使用することである。 :::info nonceはリプレイアタック対策のためのパラメータとして定義されている。 response_type に id_token を指定しなかった場合のフロー 1. Client/RP は セッションとnonceパラメータの値を紐付ける 2. Client/RP は AuthZ(AuthN) Request に nonce パラメータを含む 3. AS/OP は AuthZ(AuthN) Request に含まれた nonce パラメータを認証イベントに紐づけておき、Access Token Response に含まれる ID Token に nonce の値を含む 4. Client/RP は Access Token Response の ID Token に含まれた nonce パラメータがブラウザセッションと紐付けられた値と一致することを検証 この場合、AuthZ(AuthN) Request と Access Token Response が同一セッションに紐づいていることが確認できる。IDTokenが何かしらの手段で盗まれても(リプレイアタック)、その取得者のブラウザセッションは元のユーザーのブラウザセッションとは異なるので、nonceパラメータの検証で弾かれる。 参考 https://ritou.hatenablog.com/entry/2019/12/02/060000 OAuth 2.0/OpenID Connectで使われるBindingの仕組み(PKCE, state, nonce)について解説したritouさんの記事 ポイントは「一連の流れが同一セッションで行われたとみなせる」ようにすること。認可コードを途中で横取りしてトークンを交換されたり(認可コード横取り攻撃)、悪意のあるユーザーが発行した認可コードを別のユーザーに押し付けて実行させる(CSRF攻撃)ことなどを防ぐ。 ::: ### 15.5.3. Redirect URI Fragment Handling Implementation Notes レスポンスパラメーターがRedirection URIのフラグメント値で返される場合、クライアントはユーザーエージェントにフラグメントエンコードされた値を解析させ、それをクライアントの処理ロジックに渡す必要がある。直接的に暗号化APIにアクセスできるユーザーエージェントは、すべてのクライアントコードがJavaScriptで書かれている場合など、自己完結型である可能性がある。 :::info Redirection URIのフラグメント値で返されるのはImplicite FlowやHybrid Flowの場合のトークンや、認可コードフローのエラー値など ::: ただし、クライアントが完全にUser-Agent内で実行されない場合、これを達成する方法の1つは、それらを検証するためにウェブサーバークライアントに投げること。 以下は、クライアントがその`ridirect_uri`でホストする可能性のあるJavaScriptファイルの例である。これは、認可サーバーからのリダイレクトによって読み込まれる。フラグメントコンポーネントが解析され、その後、POSTによって受信した情報を検証および使用するURIに送信される。 以下は、リダイレクトURIのレスポンスの非規範的な例: ```javascript= GET /cb HTTP/1.1 Host: client.example.org HTTP/1.1 200 OK Content-Type: text/html <script type="text/javascript"> // First, parse the query string var params = {}, postBody = location.hash.substring(1), regex = /([^&=]+)=([^&]*)/g, m; while (m = regex.exec(postBody)) { params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); } // And send the token over to the server var req = new XMLHttpRequest(); // using POST so query isn't logged req.open('POST', 'https://' + window.location.host + '/catch_response', true); req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); req.onreadystatechange = function (e) { if (req.readyState == 4) { if (req.status == 200) { // If the response from the POST is 200 OK, perform a redirect window.location = 'https://' + window.location.host + '/redirect_after_login' } // if the OAuth response is invalid, generate an error message else if (req.status == 400) { alert('There was an error processing the token') } else { alert('Something other than 200 was returned') } } }; req.send(postBody); ``` ## 15.6. Compatibility Notes 元の仕様の初期バージョンで述べられていた潜在的な互換性の問題は、後のバージョンで解決された :::info https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html#CompatibilityNotes errata set 1の方では記述されているが、それは今のversionでは解決済み。例えばGoogleのOPによるIDTokenのiss claim値について。 > この文書を執筆している時点では, Google の OpenID Connect の実装は, ID Token の iss (issuer) Claim 値として必須となっている https:// スキームプレフィックスを省略していることに注意すべきである. Google と連携を希望する Relying Party は, この実装が更新されるまでは, これに対処するためのコードを持っている必要がある. このような問題を回避するためのコードは, Google が Issuer 値にプレフィックスを追加した場合でも, 壊れないような形式で書かれるべきである. issの定義は以下のようにhttps schemeを含むようなものになっている。 > The iss value is a case-sensitive URL using the https scheme that contains scheme, host, and optionally, port number and path components and no query or fragment components. 現在のiss値は`https://accounts.google.com`のhttpsスキームを含むようなものとなっている。 ref: https://cloud.google.com/docs/authentication/token-types?hl=ja#id errata set 1を出した当初は`accounts.google.com`だったのかと思われる。 ::: ## 15.7 Related Specifications and Implementer's Guides 以下の関連する任意の仕様は、追加の機能を提供するためにこの仕様と組み合わせて使用される可能性がある - [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) - Relying PartiesがOpenIDプロバイダに関する情報を動的に取得する方法を定義 - [OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html) - Relying PartiesがOpenIDプロバイダに動的に登録する方法を定義 - [OAuth 2.0 Form Post Response Mode](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) - HTMLフォーム値を使用してOAuth 2.0の認可レスポンスパラメーター(OpenID Connect認証レスポンスパラメーターを含む)を返す方法を定義。これらの値は、User AgentがHTTP POSTを使用して自動的に送信する。 - [OpenID Connect RP-Initiated Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) - Relying PartyがEnd-UserのログアウトをOpenIDプロバイダに要求する方法を定義 - [OpenID Connect Session Management 1.0](https://openid.net/specs/openid-connect-session-1_0.html) - postMessageに基づくログアウトやRP-initiatedログアウト機能を含む、OpenID Connectセッションを管理する方法を定義 - [OpenID Connect Front-Channel Logout 1.0](https://openid.net/specs/openid-connect-frontchannel-1_0.html) - RPページ上のOP iframeを使用しないフロントチャネルログアウトメカニズムを定義 - [OpenID Connect Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0.html) - OPとログアウトされるRP間の直接のバックチャネル通信を使用するログアウトメカニズムを定義 :::info OpenID Connect Discovery 1.0 について この仕様では、OP自身に関する情報をRPが動的に取得する方法を定義している。以下の手順が想定されている。 1. ユーザの識別子からOPのURLを取得する(OpenID Provider Issuer Discovery) 2. OPの構成情報(OpenID Provider Metadata)を取得する 1は[WebFinger](https://www.rfc-editor.org/rfc/rfc7033.html)の仕様に沿って、OPのURLを取得することが想定されている。 e.g. 1. RPにユーザが自身の識別子(例:test@example.jp)を提供 2. RPのユーザの識別子のドメインパート(example.jp)に対応するOPを検索(この部分がWebfinger) * e.g. * `https://example.jp/.well-known/webfinger? resource=test@example.jp& rel=http://openid.net/specs/connect/1.0/issuer` 3. RPが結果として返却されるOPのURLを取得 ```json { "subject": "test@example.com", "links": [ { "rel": "http://openid.net/specs/connect/1.0/issuer", "href": "https://server.example.com" } ] } ``` なお、現在の実装の多くは事前に利用可能なOPをRPが予め決めておき、それをユーザーにログイン時に選択させるような形となっているため、このフェーズが利用されることはほとんどない。(また、利用者識別子にOPのドメイン名が含まれるとは限らないので) 2の例(discovery endpoint) google: https://accounts.google.com/.well-known/openid-configuration ::: 以下の実装者ガイドは、基本的なWebベースのRelying Partyの実装者向けの独立した参考資料として機能することを意図している - [OpenID Connect Basic Client Implementer's Guide 1.0](https://openid.net/specs/openid-connect-basic-1_0.html) - OAuth Authorization Code Flowを使用する基本的なWebベースのRelying Party向けにこの仕様の一部を含む実装者ガイド。 - [OpenID Connect Implicit Client Implementer's Guide 1.0](https://openid.net/specs/openid-connect-implicit-1_0.html) - OAuth Implicit Flowを使用する基本的なWebベースのRelying Party向けにこの仕様の一部を含む実装者ガイド。