私はセキュリティエキスパートではありません。執筆当時(2024/11/22)にアクセスできる正確な情報を提供するよう努めていますが、参考程度にこちらの記事を利用していただければと思います。
また、本ドキュメントでのグラントタイプはauthorization_code
を前提としています。ネイティブアプリケーションやSPAの場合は、implicit
やhybrid
を使うこともありますが、それについては別途考える必要があることに注意してください。
1. OAuth2.0による認証は危険。。。!?
「OAuth2.0は認可のプロトコルだから、認証には使ってはだめだよ!」
こんな意見をブログや技術記事で目にすることがあります。
- 都元ダイスケ, 2018, OAuth 認証を真面目に考える | DevelopersIO
- yuto.inoue, 2024, OAuth 2.0の認証と認可とは?初心者向けにわかりやすく解説
- bwkw, 2024, OIDC を Hono × Bun で完全に理解する
- ritou, 2020, OAuth認証とは何か?なぜダメなのか - 2020冬 - r-weblife
ですが、それについて具体的にどういうことなのか、どうすれば安全に認証を実装できるのか、理解している人は少ないのではないでしょうか。
そこで、本記事ではOAuth2.0とOpenID Connect(OIDC)の違いや、OAuth2.0を使った認証の危険性、安全な認証のためのポイントについて解説します。
また、ScalaのPlay Frameworkを使ってDiscordのOAuth2.0を使った認証を実装したため、適宜参考にしていただけると幸いです。
-
makinzm/social-login-with-scala-tidb-discord: ScalaでDiscordのOAuth2.0を使った認証を実装した
- 特に次のCommitは本記事で取り上げているPKCEに関する実装のため参考になるかもしれません:security: PKCE to deal with authZ code interception attack · makinzm/social-login-with-scala-tidb-discord@fc1b11b
2. やりたいこと
説明の都合上、以下のようなシチュエーションを考えてみます。
私たちは、ユーザーがログインして自分のアカウント情報や個人設定を管理できるWebアプリケーションを開発しています。ユーザー体験を向上させるために、ソーシャルログイン機能を導入し、GoogleやFacebookなどの既存のアカウントでログインできるようにしたいと考えています。
参考:
- OAuth 2.0 Authorization Code Flow with PKCE | Docs | Twitter Developer Platform
- アクセストークンを取得する - freee Developers Community
- Microsoft ID プラットフォームと OAuth 2.0 認証コード フロー - Microsoft identity platform | Microsoft Learn
- Browser Access OAuth 2.0 Flows | Cloud Sundial
(図中の認可コードを含むリダイレクトは、実装によってはUserのブラウザに対して行われ、その後にブラウザからAppにリダイレクトされる場合がありますが、今回の例では直接Appにリダイレクトされるものとします)
このとき、OAuth2.0を使用して認証を実装していますが、「OAuth2.0は認可のプロトコルであり、認証には適していない」という意見もあり、少し不安です。
3. OAuth2.0とOIDCの違い
OAuth2.0はリソースへのアクセス許可(認可)を扱います。
OAuth 2.0 は, サードパーティーアプリケーションによるHTTPサービスへの限定的なアクセスを可能にする認可フレームワークである
The OAuth 2.0 Authorization Framework より
OpenID Connect(OIDC)は、OAuth2.0をベースにした認証プロトコルです。
OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol.
Final: OpenID Connect Core 1.0 incorporating errata set 2 より
具体的には、OIDCはOAuth2.0のフローにIDトークン(JWT形式)を追加し、ユーザーのプロフィール情報や認証イベントの詳細を安全にクライアントに伝達します。
Information about the authentication performed is returned in a JSON Web Token (JWT) [JWT] called an ID Token (see Section 2). OAuth 2.0 Authentication Servers implementing OpenID Connect are also referred to as OpenID Providers (OPs). OAuth 2.0 Clients using OpenID Connect are also referred to as Relying Parties (RPs).
Final: OpenID Connect Core 1.0 incorporating errata set 2 より
今回のシチュエーションにおいて、OIDCを使う場合以下のようになります(★マークがついている部分がOIDCの特徴です):
参考
これにより、クライアントはユーザーが誰であるかを確実に知ることができます。
4. OAuth2.0の認証は何が危険なの?
IDトークンを使わない場合、OAuth2.0を使った認証には以下のようなリスクがあります:
- 認可コードの漏洩: 認可コードが第三者に盗まれると、アクセストークンを取得されるリスクがあります。
- 認可コードの置き換え: 認可コードを第三者に盗まれ、第三者が自身の認可コードをユーザーに誤って使用させることで、アクセストークンを取得されるリスクがあります。
上記に対して、OIDCを使うと、認可コードの置き換えに対して対策をすることができます(ただし、認可コードの漏洩に対しては別途対策が必要で後述するOAuth2.0の対策と同じです)。
5. OIDCで解決できる問題
OIDCを使うことは、IDトークンを使うことと同義です。
IDトークンにはc_hash
やat_hash
(access token hash)が含まれており、これによってクライアントは認可コードやアクセストークンの置き換えを防ぐことができます。
The c_hash in the ID Token enables Clients to prevent Authorization Code substitution. The at_hash in the ID Token enables Clients to prevent Access Token substitution.
Final: OpenID Connect Core 1.0 incorporating errata set 2 より
横取りの英語はinterception
ですが、substitution
という言葉が使われていることに注意してください。
置き換えとは、攻撃者が事前に正常にログイン手順を踏んで取得した認可コードやアクセストークンを、被害者のものにすり替えることです。
横取りとは、攻撃者が通信経路上で認可コードやアクセストークンを盗み見ることです。
以下では、置き換えの問題について具体的なシナリオを考えます(参考:https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-threatmodel-08#section-4.4.1.13 )。
この例では、攻撃者は悪意のあるアプリを使って、被害者の認可コードを取得し、ターゲットアプリにログインすることで、被害者のアカウントデータにアクセスをすることを考えます。
まずは、OIDCを使わない場合のシナリオを考えます。
しかし、上記のシナリオはOIDCを利用すると防ぐことができます。
アプリ上で行うIDトークン検証の具体的な方法は、IDトークンのc_hash
やat_hash
, nonce
を使って、認可コードやアクセストークンが正しいものであるかを確認することです。
認可コードが正しいものという意味は、sub
が正しいものであることを確認することです。
今回のケースでは、認可コードを発行したのは攻撃者ではなく被害者であるため、
上記の図の(★)で発行したIDトークンと、被害者の認可コードを使って発行したIDトークンを比較し、c_hash
やat_hash
を使って認可コードやアクセストークンが正しいものであるかを確認することで、攻撃者のアクセスを拒否することができます。
sub
REQUIRED. Subject Identifier. A locally unique and never reassigned identifier within the Issuer for the End-User, which is intended to be consumed by the Client, e.g., 24400320 or AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4. It MUST NOT exceed 255 ASCII [RFC20] characters in length. The sub value is a case-sensitive string.
https://openid.net/specs/openid-connect-core-1_0.html?utm_source=chatgpt.com#IDToken より
In OpenID Connect, this is mitigated through mechanisms provided through the ID Token. The ID Token is a signed security token that provides Claims such as iss (issuer), sub (subject), aud (audience), at_hash (access token hash), and c_hash (code hash). Using the ID Token, the Client is capable of detecting the Token Substitution Attack.
https://openid.net/specs/openid-connect-core-1_0.html?utm_source=chatgpt.com#TokenSubstitution より
5.1. 補足 : OIDCで防げない例
上記の例は、攻撃者が攻撃を開始する前にIDトークンを検証しているため、攻撃者のアクセスを拒否することができました。
しかし、IDトークンの検証が通ってしまうような以下のようなパターンはOIDCだけでは防げません。
- 被害者がまだターゲットアプリを利用していない場合
- 被害者のアカウントがターゲットアプリに登録されていない場合、攻撃者は被害者のアカウントを作成できてしまいます。
- 攻撃者が初めてターゲットアプリにログインする場
- 攻撃者が初めてターゲットアプリにログインする場合、IDトークンの不整合が起きないため、攻撃者のアクセスを拒否することができません。
そのため、OIDCに対しても、nonce
や後述するようなPKCE
を使う必要があります(本記事ではnonce
については触れませんが、nonce
はIDトークンの検証に使われるパラメータです。OpenID Connectのstateとnonceの違いがわからなかった #openid_connect - Qiitaの記事などが参考になるかと思います)。
6. OIDCを利用しないで、OAuth2.0の認証を安全に実装するには
OAuth2.0において、グラントタイプが authorization_code
の場合はそもそも、認証コードを使ってアクセストークンを取得するため、アクセストークンはクライアント側には漏洩はされません。
The authorization code provides a few important security benefits,
such as the ability to authenticate the client, as well as the
transmission of the access token directly to the client without
passing it through the resource owner's user-agent and potentially
exposing it to others, including the resource owner.
https://datatracker.ietf.org/doc/html/rfc6749#section-1.3 より
そのため、認可コードの漏洩や置き換えに対して対策をすれば、OAuth2.0の認証を安全に実装することができます。
そこで利用できるのが、PKCE(RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients)です。
PKCE(pixy)とは、認可コードフローを使用する際に、認可コードを取得する際に使用するランダムな文字列(code_verifier
)を生成し、そのハッシュ値を認可リクエストに含めることで、認可コードの横取りを防ぐための仕組みです。
To mitigate this attack, this extension utilizes a dynamically
created cryptographically random key called "code verifier". A
unique code verifier is created for every authorization request, and
its transformed value, called "code challenge", is sent to the
authorization server to obtain the authorization code. The
authorization code obtained is then sent to the token endpoint with
the "code verifier", and the server compares it with the previously
received request code so that it can perform the proof of possession
of the "code verifier" by the client. This works as the mitigation
since the attacker would not know this one-time key, since it is sent
over TLS and cannot be intercepted.
https://datatracker.ietf.org/doc/html/rfc7636#section-1 より
(★マークがついている部分がPKCEの特徴です)
参考
具体的なパラメータとしては、code_challenge
とcode_verifier
などがあります。
https://datatracker.ietf.org/doc/html/rfc7636#section-6.1 より
こちらを使うことで、認可コードの横取りを防ぐことができます。
また、この方法は認可コードの置き換えにも対応しています。
というのも、認可コードを発行する際のcode_challenge
は、code_verifier
をハッシュ化したものであり、code_verifier
はランダムな文字列であるため、攻撃者が認可コードを盗んでも、code_verifier
を知らない限り、アクセストークンを取得することができないためです。
OIDCでも認可コードの横取りなどについて対策が必要です。詳細は、OAuth・OIDCへの攻撃と対策を整理して理解できる本(リダイレクトへの攻撃編 [2023年改訂版] - Auth屋 - BOOTHなどをご参照ください。
よって、OIDCを使わなくても、OAuth2.0の認証を安全に実装することができます。
7. まとめ
OIDCを使わなくてもPKCEを使うことで安全に認証を実装することができます。
8. 感想
確かにOAuth2.0は認可のプロトコルであるため、私も認証に使うのは危険だと思っていましたが、PKCEを使うことで安全に認証を実装できると2024/11/23現在は判断しています。一方で、何がどう危険かを確認し考え実装することは、どの手法を使うにしても重要だと感じています。