GitHub の OAuth 実装の仕様違反とセキュリティ上の考慮事項

本稿は [GitHub Docs](https://docs.github.com/) の "[Authorizing OAuth Apps](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps)" ページに書かれている情報に基づいています。英語版はこちら → "[Spec Violations in GitHub OAuth Implementation and Security Considerations](https://darutk.medium.com/spec-violations-in-github-oauth-implementation-and-security-considerations-48a7f8dfa335)"

# 仕様違反箇所

1. 認可リクエストの `response_type` リクエストパラメーターがない。当パラメーターは必須である。[RFC 6749](https://www.rfc-editor.org/rfc/rfc6749.html) (The OAuth 2.0 Authorization Framework) [Section 4.1.1](https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.1) (Authorization Request) 参照。

2. トークンレスポンスのデフォルトフォーマットが `application/x-www-form-urlencoded` のようである。フォーマットは常に `application/json` でなければならない。[RFC 6749](https://www.rfc-editor.org/rfc/rfc6749.html) (The OAuth 2.0 Authorization Framework) [Section 5.1](https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1) (Successful Response) 参照。

3. トークンレスポンスの `scope` レスポンスパラメーターに列挙されている値がカンマで区切られている。区切り文字はスペースでなければならない。[RFC 6749](https://www.rfc-editor.org/rfc/rfc6749.html) (The OAuth 2.0 Authorization Framework) [Section 3.3](https://www.rfc-editor.org/rfc/rfc6749.html#section-3.3) (Access Token Scope) 参照。

4. トークンレスポンスが `"token_type":"bearer"` と言っているにも関わらず、リソースアクセス時の `Authorization` ヘッダーのフォーマットが `Authorization: token OAUTH-TOKEN` である。フォーマットは `Authorization: Bearer OAUTH-TOKEN` でなければならない。[RFC 6750](https://www.rfc-editor.org/rfc/rfc6750.html) (The OAuth 2.0 Authorization Framework: Bearer Token Usage) [Section 2.1](https://www.rfc-editor.org/rfc/rfc6750.html#section-2.1) (Authorization Request Header Field) 参照。

5. デバイス認可レスポンスのデフォルトフォーマットが `application/x-www-form-urlencoded` のようである。フォーマットは常に `application/json` でなければならない。[RFC 8628](https://www.rfc-editor.org/rfc/rfc8628.html) (OAuth 2.0 Device Authorization Grant) [Section 3.2](https://www.rfc-editor.org/rfc/rfc8628.html#section-3.2) (Device Authorization Response) 参照。

6. デバイスフローにおけるトークンレスポンスのデフォルトフォーマットが `application/x-www-form-urlencoded` のようである。フォーマットは常に `application/json` でなければならない。[RFC 8628](https://www.rfc-editor.org/rfc/rfc8628.html) (OAuth 2.0 Device Authorization Grant) [Section 3.5](https://www.rfc-editor.org/rfc/rfc8628.html#section-3.5) (Device Access Token Response) 参照。当セクションは _"If the user has approved the grant, the token endpoint responds with a success response defined in [Section 5.1](https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1) of [[RFC6749](https://www.rfc-editor.org/rfc/rfc6749.html)];”_ と言っている。

7. デバイスフローにおけるトークンレスポンスの `scope` レスポンスパラメーターに列挙されている値がカンマで区切られている。区切り文字はスペースでなければならない。トークンエンドポイントの実装は [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749.html) に準拠すべきだと [RFC 8628](https://www.rfc-editor.org/rfc/rfc8628.html) は言っている。

# GitHub 独自拡張

1. 認可リクエストの `login` パラメーター。GitHub 独自のパラメーターを導入するよりも、[OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) [Section 3.1.2.1](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) (Authentication Request) で定義されている `login_hint` を活用したほうがよい。

2. 認可リクエストの `allow_signup` パラメーター。GitHub 独自のパラメーターを導入するよりも、[Initiating User Registration via OpenID Connect](https://openid.net/specs/openid-connect-prompt-create-1_0.html) で定義されている `prompt=create` を活用したほうがよい。

3. トークンリクエストの `Accept` ヘッダーの値に従って、トークンエンドポイントがレスポンスのフォーマットを変更している。否。フォーマットは常に `application/json` でなければならない。

4. デバイス認可リクエストの `Accept` ヘッダーの値に従って、デバイス認可エンドポイントがレスポンスのフォーマットを変更している。否。フォーマットは常に `application/json` でなければならない。

5. デバイスフローにおいてトークンエンドポイントが返す可能性のあるエラーコードの内、次のものは GitHub 独自のもので、標準仕様では定義されていない → `incorrect_client_credentials`, `incorrect_device_code`, `device_flow_disabled`

# セキュリティ上の考慮事項

1. [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636.html) (Proof Key for Code Exchange by OAuth Public Clients) をサポートすべきである。具体的には、認可エンドポイントは `code_challenge` リクエストパラメーターと `code_challenge_method` リクエストパラメーターを、トークンエンドポイントは `code_verifier` リクエストパラメーターをサポートすべきである。この仕様は、攻撃者が盗んだ認可コードをトークンエンドポイントでアクセストークンと交換することを防ぐ。詳細は『[PKCE: 認可コード横取り攻撃対策のために OAuth サーバーとクライアントが実装すべきこと](https://qiita.com/TakahikoKawasaki/items/00f333c72ed96c4da659)』を参照のこと。

2. [RFC 8705](https://www.rfc-editor.org/rfc/rfc8705.html) (OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens) [Section 3](https://www.rfc-editor.org/rfc/rfc8705.html#name-mutual-tls-client-certifica) (Mutual-TLS Client Certificate-Bound Access Tokens) をサポートすべきである。**GitHub がこの証明書バインディングの仕様をサポートしていれば、先のセキュリティインシデント** ("[Security alert: Attack campaign involving stolen OAuth user tokens issued to two third-party integrators](https://github.blog/2022-04-15-security-alert-stolen-oauth-user-tokens/)") **の衝撃をかなり軽減できていたであろう**。この仕様は、攻撃対象のクライアントアプリケーションのクライアント証明書の窃取にも成功していない限り、攻撃者が盗んだアクセストークンを用いてリソースにアクセスすることを防ぐ。証明書バインディングの説明については『[実装者による Financial-grade API (FAPI) 解説](https://qiita.com/TakahikoKawasaki/items/83c47c9830097dba2744)』を参照のこと。

3. より安全なクライアント認証方式をトークンエンドポイントとデバイス認可エンドポイントでサポートすべきである。[RFC 6749](https://www.rfc-editor.org/rfc/rfc6749.html) の [Section 2.3](https://www.rfc-editor.org/rfc/rfc6749.html#section-2.3) (Client Authentication) で説明されている伝統的なクライアント認証方式は、クライアントシークレットがネットワーク上を流れる。これは避けるべきである。実際に、[Financial-grade API](https://fapi.openid.net/) 仕様では伝統的なクライアント認証方式は禁止されている。より安全なクライアント認証方式については『[OAuth 2.0 クライアント認証](https://qiita.com/TakahikoKawasaki/items/63ed4a9d8d6e5109e401)』を参照のこと。

4. 2012 年 10 月の RFC 6749 の公開 (約 10 年前!) 以降、他にも多くのセキュリティ向上のための仕様が策定された。例えば、**JAR** ([RFC 9101](https://www.rfc-editor.org/rfc/rfc9101.html) The OAuth 2.0 Authorization Framework: JWT-Secured Authorization Request (JAR))、**JARM** ([Financial-grade API: JWT Secured Authorization Response Mode for OAuth 2.0 (JARM)](https://openid.net/specs/openid-financial-api-jarm.html))、**PAR** ([RFC 9216](https://www.rfc-editor.org/rfc/rfc9126.html) OAuth 2.0 Pushed Authorization Requests) など。**[OpenID Foundation](https://openid.net/)** の[ワーキンググループ](https://openid.net/wg/)のエキスパートに相談すべきである。