Re: OAuth 2.0のclient_secretって本当に秘密鍵ですか?

昨日こんな記事を見かけたので、記事にまとめることにします。

OAuth2.0のclient_secretって本当に秘密鍵ですか?

元記事にあるとおり、現状Native AppでのOAuth 2.0の実装は、API提供者・利用者ともにポリシーがバラバラで、混乱の元になっていると思います。

Googleのドキュメントにも「the client_secret is obviously not treated as a secret.」とあるわけだけど、そのくせclient_secretを使ってるし、ネットで調べても少なくない数の人がアプリに埋め込んでるので、client_secretを公開したときの問題を考えてみる。

“offline” アクセスと “online” アクセス

Googleは、“offline access” に対して以下のようなポリシーを持っています。

Upcoming changes to OAuth 2.0 endpoint - Google Apps Developer Blog

openid / connect / issues / #539 - Messages - 0. Add scope for offline access - Bitbucket

上の記事では議論が長くてかつ英語なので、簡単に要約すると、

  • access tokenが無効化されると、clientは新しいaccess tokenを取得しなければならない。
  • 新しいaccess tokenを受け取るには、ユーザーがその場にいる必要がある。ユーザーが既にclientのアクセスに同意している場合は、同意画面をスキップさせることができるので、ユーザーが毎回Googleの同意画面を見る必要は無い。
  • JS Appなど、ブラウザ内で動作していて常にユーザーとインタラクションしているclientは、同意画面さえスキップできればリダイレクトベースで必要に応じて毎回access tokenを取得すればよい。
  • スマホ上のNative Appの場合は、UX的に毎回ブラウザ経由してaccess tokenを取得するのはつらいので、ユーザーから明示的に “offline” アクセスへの同意を得て、次回以降はrefresh tokenを使って新しいaccess tokenを取得すればよい。

そして、Googleは、refresh tokenを伴う場合を “offline” アクセス、伴わない場合を “online” と定義しています。詳細は異なるものの、似たような定義はFacebookやAOLなども行っています。

Native Appに埋め込まれたclient_secretは簡単に漏洩する

このご時世 Charles などを使えば、自分のデバイス上で行われているSSLリクエストなら簡単に覗き見ることができます。

OAuth 2.0では、authorization codeとaccess tokenを交換する時とrefresh tokenをaccess tokenと交換する時に、client_secretを平文でAuthorization Server (この場合はGoogle) に送信します。

そのため、上記2つのいずれかのリクエストがiPhone上のNative Appから発行されるなら、Jailbreakや逆コンパイルなどせずともエンドユーザーなら誰でも、そのアプリに埋め込まれたclient_secretを知ることができます。

OAuth 2.0のclient_secretをアプリに埋め込んで配布するというのは、client_secretが漏洩する前提でそれを扱っているということです。そしてその場合、当然以下のような疑問がでてきます。

client_secretが漏洩してはいけないのか?

これに関しては、例えばFacebook Graph APIの場合であれば、client_secretが漏洩すると以下のようなリスクがあります。(個人的にGoogle APIは使ってないので、Googleに関しては僕はよく把握していません)

client_secretが漏洩した場合のリスクはAPI提供者側のポリシーに依存するため、Generalな解答をするとすれば以下のようになるでしょう。(ちなみに、いまClient Credentials Flowがサポートされていないからといって、将来にわたってその状態が維持される保証があるわけではないです。その場合はclient_secretを発行しなおしたりするんでしょうかね? > だれとなく)

全てのAPI提供者がNative Appに埋め込まれたclient_secretが漏洩することを前提としたアクセスコントロールを実施しているわけでも無いようですし、そもそもNative Appにclient_secretを埋め込むことを禁止しているFacebookのようなOAuth Serverも存在する (=> 本来こっちの方が一般的であるべき) ので、Developerにとっては混乱の元でしょうね。

Native Appにclient_secretを埋め込む前提で、その漏洩の可能性という点で見れば、OAuth 1.0を使った方が良い場合もあるでしょう。OAuth 1.0では、Native Appにclient_secretを埋め込んでもそれは署名計算に使われるだけで直接OAuth Serverに送信されることはありません。かといっていまからOAuth 1.0を採用するというのは、時代の流れ的にどうなんだということもありますが。

GoogleとFacebookのスタンスの違い

GoogleがNative App (= Installed Application) 用のフローを用意してNative Appにclient_secretを埋め込むように案内する一方、Native AppにはFB Official Appと連携したフローを用意してclient_secretを不要にしているFacebookの存在は、対照的です。

Googleの場合は、Client登録時にClientがWeb AppなのかNative Appなのかといった分類をさせているはずで、そのclient typeによってclient_secretを使ってできることに差を付けているのかもしれません。(要確認: Google APIよく知らないので、この辺詳しい人いたら教えてほしい)

Facebookの場合は、Native AppとWeb App、Facebook iFrame Appといった複数の種類のClientを、提供者が同一であれば同じclient_id (& client_secret) を使い回せるようにして、cross-platformな環境でアプリを提供しやすくしているため、client typeによってclient_secretの価値を変えるということはやりづらいのかも知れません。(じゃあGoogleはcross-platformなClientに対してどう考えてるの?ってのは、Googlerに直接聞いてみたいかも)

client_secretが漏洩した時に被害を受けるのは誰?

これもまた各OAuth ServerがどんなAPIを提供していて、OAuth 2.0のClient Credentials Flowを使って得たaccess tokenで何ができるのかに依存するので、一概には言えないのですが、よくあるケースとしてはAPI利用状況のAnalytics情報を取得したりするAPIが考えられるので、被害を受けるのはエンドユーザーというよりはClient Developer自身であることの方が多いでしょう。

client_secretを埋め込む実装をしているOAuth Client Developerは、一度利用しているAPIがClient Credentials Flowをサポートしているのか、Client Credentials Flowで得たaccess tokenでは何ができるのか、一度APIドキュメントを確認したりAPI提供者に問い合わせてみた方が良いかも知れません。

Facebookの用にclient_secretさえあれば任意のユーザーをBanできてしまったりする場合は、client_secretが漏洩することでClient Developerとエンドユーザー両方が被害を受けることもありえます。

蛇足: オレはこう思う!(だっけ?)

まぁこの辺りはOAuth 1.0からOAuth 2.0になってServer / Client双方にいろいろ選択肢が増えたので、各社バラバラな仕様になってしまって

When compared with OAuth 1.0, the 2.0 specification is more complex, less interoperable, less useful, more incomplete, and most importantly, less secure.

OAuth 2.0 and the Road to Hell

っていう前OAuth 2.0 Authorの彼の意見も、あながち無視できないところではある。