Hatena::ブログ(Diary)

r-weblife RSSフィード Twitter

2012/02/19

Yahoo! JAPANの複数プロフィール対応は実に興味深い

こんばんは、ritouです。

最近、Yahoo! JAPANがこのような対応を行ったそうです。

Webサービス(特にSNS)における複数プロフィール対応ってのは実際のところどうなのでしょうか。

Yahoo! JAPANは今までどういう状態だったのか?

ここからの内容は「YBBのADSLも使ってて何かのときに500円戻ってきたこともありつつ、特に一昨年秋ぐらいまでまでヘビーなYahoo! JAPANユーザーだった」私が考えた個人的見解となります。

Yahoo! JAPANにはアドレスブックやメールといった個人のツール、知恵袋や掲示板、グループなどの以前からある他人とのコミュニケーションツール、Yahoo!プロフィールを中心としたSNS機能、あと動画とか、もう区切り方がわからないほどたくさんのサービスがあります。

サービスを利用する際の"人格"という観点でいうと、以下のように整理されていたと認識しています。

  • (1) ログインしていれば利用可能、特に他人とのかかわりを意識する必要がないサービス
  • (2) Yahoo! JAPAN IDの他に"ニックネーム"と呼ばれるYahoo! JAPAN IDと同等?に扱われる文字列を識別子として投稿などを行うサービス
  • (3) Yahoo! JAPAN ID単位で識別子だけではなくより人格を意識した"プロフィール"を持ち、友人とのつながるしくみを持つサービス

不動産などで物件を探すときは別に他人から見た自分を意識する必要はないわけですね(1)。

掲示板など他人とコミュニケーションをとるサービスではユーザー識別子と発言などが結びつきます(2)。

ある時期以降、より人格を中心にフィードのしかけが用意されたSNS的なサービスが出てきました(3)。

この流れ、なにやらWebサービスの歴史を感じられるような気もしないでもないですね。

記事にあるとおり、Yahoo! JAPAN IDに対応するプロフィールはあったものの、ニックネームに対してのプロフィールはありませんでした。

(2)で複数の人格を使い分けていたユーザーが(3)のサービスを利用しようとすると一つの人格しか選択できませんので若干違和感を感じていた可能性があります。

また、Yahoo! JAPAN内のサービス間連携に支障があったかのではないかと思われます。

例えばYahoo! JAPAN ID/ニックネーム単位で利用できるYahoo!メッセンジャーでは、けっこう前に「プロフィールを見る」というリンクが付きました。

しかし、ニックネームで登録している友人はリンクさせるプロフィールが存在しませんでしたし、メッセンジャー上のつながりがSNSのソーシャルグラフとは別の扱いになっている状況でした。

部外者が「どうしてこうなった」を考えてもしょうがないわけですが、今回のYahoo! JAPAN ID + ニックネームの数だけプロフィールを作成するという対応はこれらのカオスな状態を解消するための手段だと考えられます。

Yahoo! JAPAN ID/ニックネームとプロフィールの紐付けは可能か?

以前から、オークションやニュースのコメントなど、Yahoo! JAPAN ID/ニックネームを利用するサービスでは文字列の一部を*でマスクしたものがページに表示されたりしていました。

これは、Yahoo! JAPAN IDがメールアドレスの@以前、つまりコンタクト先の一部になっているユーザーがいるため、Yahoo! JAPAN ID/ニックネームそのものは隠したかったのではと思います。

個人のプロフィールページのURLなどにはYahoo! JAPAN IDではない(一見ランダムに見えてまぁ意味ありげな)文字列が利用され、Yahoo! JAPAN IDとの連携を意識させない意図が感じられました。

オークションなどの既存のサービスとソーシャル系のサービスとの紐付け(名寄せ)が行われることによるリスク軽減を狙っていたようにも思われますが、しかし実際は完全に切り離すことはできていませんでした。

SNS系のサービスであるブログのURLにはYahoo! JAPAN IDであったり(私もブログを開設したことがありますがニックネームも利用できたかどうかは覚えていない)、上記メッセンジャーのようにもともとYahoo! JAPAN ID/ニックネームベースのサービスとソーシャル系のサービスの相互連携を進める上でこれらの紐付けは隠せない状況です。

ということで、現状Yahoo! JAPAN ID/ニックネームを識別子としているサービスとプロフィールを利用するサービス間で情報の紐付けが可能なケースが存在しますので覚えておいていただければと思います。

そういえば、少し前にニュースのコメントにYahoo! JAPAN ID丸出し+表示名載せます騒ぎの際に似たような指摘をされている方がいました。

Yahoo! JAPAN IDとニックネーム間の紐付け(名寄せ)は可能か?

もともと紐付けさせられないためのしくみであると思います。

紐付けを知っているのはサービス提供もとのYahoo! JAPANと本人のみです。

ひとつのIDで七人のなんとかってページにあるように、Yahoo! JAPANの対応済サービス内では自分で切り分けた人格単位で思う存分楽しんでいただければと思います。

外部アプリケーションへのIdentity連携はどうなる?

私がざっくり確認したところ、まだ今回の複数プロフィール対応に関した変更は見られませんでした。

  • OpenIDのユーザー識別子として渡されるものは一種類
  • OAuth後に渡されるGUIDとして渡されるものも一種類
  • OpenIDのAXで渡されるプロフィール情報は、Yahoo! JAPAN IDに紐づいたプロフィール

Yahoo! JAPANは今後、ニックネームに紐づいたプロフィールについても、外部サービスと連携させようとするでしょう。せっかくなのでしたほうが良いと思います。

その際に、私が考える気をつけなければいけない点が2点あります。

  • (1) Yahoo! JAPAN ID/ニックネームに紐づいたプロフィール単位で外部サービスに渡すユーザー識別子も別にする
  • (2) 課金/決済を利用するサービスや、1ユーザーで複数アカウント登録を許可させないアプリケーション向けに「同一アプリケーション内では1ユーザー1アカウントを利用させる」オプションを用意する

(1)は、「Yahoo! JAPAN ID/ニックネームの紐付けを知っているのはサービス提供もとのYahoo! JAPANと本人のみ」としておくためです。

Yahoo! JAPAN内で別人格になっているものを外部アプリケーションで名寄せされてしまっては意味がありません。

(2)は、Yahoo! JAPAN内で「Yahoo!ウォレット」「Yahoo!ポイント」といった決済系のしくみが「1ユーザー1アカウント」になっているため、その機能を外部に提供する場合も同じポリシーにすべきという意味です。

外部アプリケーション側のポリシーで「1ユーザー1アカウント」になっている場合もこの方法で対応できるでしょう。

オプションのパラメータを受け取ったとき、ユーザーに対して「このアプリケーションには複数のプロフィールによるアカウント連携ができません。利用するプロフィールを選択してください。」といった旨を示し、利用するプロフィールを選択させるような対応でいいかと思います。

まぁこれぐらいのことは中の人たちはわかっていると思いますが、「特に一昨年秋ぐらいまでまでヘビーなYahoo! JAPANユーザーだった」私としてはYahoo! JAPAN様にサービス内外での不整合が発生しない状態を保ち、これからも大規模サービスを展開し続けていただきたいと思っております。

ここまでで、いったん説明はおしまい。

今回のYahoo! JAPANがやったことは有名どころのSNSでは実装されていません。

なぜこのような実装は進んでいないのでしょうか?

SNSにおける複数プロフィール対応は実際どうなのか?

すぐに思いつくところでFacebookの公開範囲設定やGoogle+のサークル管理でフィードの公開先を管理することが可能ですが、友達一覧は共通だったり、プロフィール情報を友達/仕事仲間/趣味用の友達向けに管理できるサービスはほとんどないと思います。

Yahoo! JAPANのような対応を行うメリットはこのあたりかと。

  • プロフィールやソーシャルグラフを完全に分けられることで、適切なプライバシー管理が可能
  • 外部アプリケーションにリソースを提供している場合でも、名寄せのリスクを防ぐことが可能

プライベートな仲間同士の発言から会社に結びついて大きな問題に発展してしまうこともよくあるこの世の中で、個人的にはわりとこれだけでもメリットがあるように思えます。

ではなぜ他のところでは実装されていないのか。

SNSにおける複数プロフィール対応のデメリット

デメリットというか課題は使い勝手でしょう。単一プロフィールのサービスを使い続けてきたユーザーに細かくプロフィールを分けて利用してもらえるでしょうか。

とはいえ、現在も、利用規約により複数アカウント作成が許可されているサービスでは内容ごとに複数のアカウントを作って利用されている方もいると思いますし、そういう方が管理するパスワードを一つ減らせるだけでもやる価値は多いのではと思ったりします。

既存のSNSを分割するのは大変だと思いますので、今後新規にSNSを作られる方はこの複数プロフィール機能について検討していただくのも良いのではないかと思います。

Y!Jの件の説明書いてるうちに疲れて尻すぼみになってしまいました。ではまた。

追記 : 今年はこのあたりの議論がいろんなところで出てきそうな印象

このエントリだけでは特に面白みはないが、このあたりの話は日ごろから何度も出ている。

LINEの記事にもこんなのあった。

フェイスブックは実名制に意味があるという一石を投じた一方、1つの顔じゃないといけないというプレッシャーを与えた。多面的な顔を出せない場になりつつあり、そこに潜在的な不満が生まれた。対してLINEは、『そうじゃないんだよ。人にはいろんな顔があるのは当然』という考え方。例えば中学ではオタクだったけど、大学ではチャラ男になった場合、それぞれのグループは絶対に交わってはいけない。数百人の友達と平均的につきあうのは無理がある」

スマホが拓く世界市場 和製「LINE」ヒットの裏側  :日本経済新聞

LINEは電話帳の電話番号、つまり既存の関係性をベースに友達関係を構築していく。その中で、さらにプライベートで閉じたグループを作り、グループ内の全員で音声通話やチャットを楽しむこともできる。よそ行きの1つの顔に疲れたユーザーに、「グループによって違う顔を使い分けてください」というメッセージが受けたという分析だ。

スマホが拓く世界市場 和製「LINE」ヒットの裏側  :日本経済新聞

これだけ見たらLINEすごそうだけどグラフの作り方が違うだけでグループの考え自体はFacebookにもあるのでいろいろ考えるところはある。

あと。。。武雄市の職員のFacebookアカウントの利用についてとかもこの辺に絡めた話できそう。

2012/02/06

OAuth 2.0 Implicit Flowをユーザー認証に利用する際のリスクと対策方法について #idcon

おはようございます、ritouです。

今回は、一部で先週話題なりましたOAuth 2.0のImplicit Flowについてのエントリになります。

(2012/2/7 いろいろと修正しました。)

今回は以下の内容について整理したいと思います。

OAuth 2.0 Implicit Flowとは

OAuth 2.0ではサードパーティーアプリケーションが保護リソースへのアクセス権限を得るためのいくつかのフローが定義されています。

(仕様中ではFlowやGrant Typeなどという用語が入り混じっていますがここではフローという表現を利用します)

その中に、

  • 認可サーバー,サードパーティークライアント,エンドユーザーの3者が存在
  • サードパーティークライアントはユーザーの保護リソースへのアクセス権限を要求
  • ユーザーが認可サーバーが提供する画面上でアクセス権限委譲に関する同意処理を行う

というフローがあります。

例として、大手写真共有サイト(サーバー)への画像アップロードを行うアプリケーションを考えます。

それらのアプリケーションの設定時に一旦写真共有サイト上でエンドユーザーの同意を行います。この同意処理により、アプリケーションは画像のアップロード権限を取得できます。

仕様では、アプリケーションの特性により2つのフローに分かれています。

  • Authorization Code Flow
  • Implicit Flow

"Authorization Code Flow"は、Webサーバー上で動作するWebアプリケーションなど、サーバー-クライアント間の共有秘密鍵"Client Secret"を安全に保管できる(ことになっている)場合に利用するフローです。

仕様にある図を引用します。

f:id:ritou:20120206082225p:image:w360

リソースアクセスのためのチケットにあたるアクセストークンを取得する処理の中で、(D),(E)で示される認可サーバー-クライアント間の直接通信にClient Secretを利用します。

一方、iPhone/Androidアプリケーションなど、リバースエンジニアリングが可能でありClient Secretを安全に保持することができない場合にはImplicit Flowを利用します。

JavaScriptで記述され、Ajaxを利用して動作するアプリケーションなども同様にImplicit Flowを利用します。

こちらも、仕様にある図を引用します。

f:id:ritou:20120206082227p:image:w360

図中でWeb-Hosted Client Resourceとありますが、ユーザーの同意後にアクセストークンがURIフラグメント識別子に付加されてクライアントに渡されます。

Authorization Code FlowにあるようなClient Secretを用いた直接通信は存在しません。

後者のImplicit Flowを利用した"あるユースケース"に潜むセキュリティホールについて説明します。

OAuth 2.0をユーザー認証に利用するケース

世の中には"OAuth認証"と呼ばれる、OAuthのフローをユーザー認証機能に利用するアプリケーションが存在します。

さきほどの写真共有サイトの例でいうと、

  • 「○○でログイン」というリンクをクリックして毎回OAuth 2.0の処理を行い、アクセストークンを取得
  • 取得したアクセストークンをサーバーが提供するユーザープロフィールAPIに送り、ユーザー情報を取得
  • 取得したユーザー情報に基づき、アプリケーション上のユーザーとの紐付けを行いログイン状態とする

というようなものです。

このようなユースケースにおいて、"同意したユーザー"="プロフィールAPIが情報を返すユーザー"である場合は、プロフィールAPIの結果を利用してログイン状態としても問題ありません。

ただし、OAuthのフローにおいて、「エンドユーザー△△(user_id)がクライアント□□(client_id)に対してリソース☆☆(scope)についてのアクセス権限委譲に同意した」という結果はやりとりされず、クライアントはアクセストークンのみを取得します。

クライアントは「アクセストークンが自らに対して発行されたもので、同意処理をしたユーザーのリソースへのアクセス権限を持つ」ことが保障されている(確認できる)必要があります。

不正なアクセストークンを取得/利用してしまうリスク

クライアントが意図しないアクセストークンを取得してしまうと、別のユーザーのプロフィールAPIの結果を用いてログイン状態とする、つまりなりすましが可能になります。

Authorization Code Flowの場合

Authorization Code Flowにおいて意図しないアクセストークンを受け取るような攻撃を行うためには、Authorization Codeの値から別のユーザーのものに置き換えることが可能です。

アクセストークン取得までの間にClient Secretを用いた直接通信を行いますので、別のクライアント向けに発行されたAuthorization Codeに置き換えた場合は認可サーバー側のチェックでエラーとなります。

同じクライアント向けに別ユーザーが同意処理を行い発行されたAuthorization Codeに置き換えることで、「エンドユーザー△△が」という部分を別のものに置き換えることが可能です。

ほとんどのユースケースでAuthorization Codeの受け渡しはリダイレクトURIに付加され自動で行われます(デバイスをまたぐ場合など画面に表示して手動でコピペなどもあります)。

認可サーバーはAuthorization Codeの有効期限を短く設定する、ワンタイムのものとして何度も利用しないなどの対策でこのリスクを低減するような実装をする必要があり、実際にたいていの認可サーバーはそのように実装しているようです。

仕様では下記のようにあります。

認可コードは認可サーバーによって許可される. 漏洩のリスクを軽減するため, 認可コードは発行されてから短期間で無効にしなければならない (MUST). 認可コードの有効期限は最大でも10分を推奨する (RECOMMENDED). クライアントは2回以上認可コードを使用してはならない (MUST NOT). もし認可コードが2回以上使用された場合は, 認可サーバーはリクエストを拒否しなければならず (MUST), この認可コードを基に発行されたこれまでのすべてのトークンを無効化すべきである (SHOULD). 認可コードはクライアント識別子とリダイレクトURIに紐づく.

Implicit Flowの場合

Implicit Flowでは認可サーバーが発行してアクセストークンをリダイレクトURIのフラグメント識別子として間接的を受け取るため、エンドユーザーのブラウザ含めて漏洩や置き換えのリスクが大きいと言えるでしょう。

アクセストークンの置き換えにより、「エンドユーザー△△がクライアント□□に対してリソース☆☆について」という項目を別のものに置き換えることが可能です。

仕様では下記で言及されている内容に相当します。

クロスサイトリクエストフォージェリ (CSRF) は, 攻撃者が犠牲となるエンドユーザーのユーザーエージェントに (例えば, ユーザーエージェントに誤解を招きやすいリンクやイメージ, 転送によって) 悪意のあるURIを閲覧させることにより (通常, 有効なセッション・クッキーの存在によって) 信頼が確立されたサーバーへ接続させる手法である.

クライアントのリダイレクトURIに対するCSRF攻撃は, 攻撃者が自身の認可コードやアクセストークンを紛れ込ませることを可能とし, クライアントに犠牲者の保護されたリソースではなく, 攻撃者のリソースに紐付いたアクセストークンを使わせることが出来てしまう (例えば, 犠牲者の銀行口座情報を攻撃者の管理しているリソースへ保存してしまう, といったことも可能となる).

CSRFへの対策が(MUST)になっていますが、(SHOULD)としてstateパラメータの利用が明記されています。

クライアントは自身のリダイレクトURIに対してCSRF保護対策を導入しなければならない (MUST). 一般的に保護対策は, リダイレクトURIのエンドポイントへ送られたすべての要求に対して, 要求とユーザーエージェントの認証状態を紐付けるための値を含めることにより実現する (例えば, ユーザーエージェントを認証するために使うセッションクッキーのハッシュなど). クライアントは認可要求の発行時, この値を認可サーバーへ伝搬するために state リクエストパラメーターを利用すべきである (SHOULD).

一旦エンドユーザーの認可が得られると, 認可サーバーはエンドユーザーのユーザーエージェントを state パラメーターに含まれる要求されたバインド値と共にクライアントへリダイレクトする. クライアントはバインド値とユーザーエージェントの認証状態を突合することによりリクエストの正当性を確認することが出来る. CSRFを防ぐために使用されるバインド値は推測不能な値を含まねばならず (MUST), ユーザーエージェントの認証状態 (例えば, セッションクッキーやHTML5のローカルストレージ) はクライアントおよびユーザーエージェントのみがアクセスできる状態に保たれなければならない (つまり, 同一起源ポリシーによる保護) (MUST).

しかし、stateパラメータではアクセストークンの置き換えを防ぐ手段にはなりえません。

ブラウザからクエリで送られたものをHTTP Headerを監視するアドオンなどで抜き出し、レスポンスのフラグメント識別子を生成することが可能です。

また、アクセストークンとstateパラメータの組み合わせの検証まではOAuth 2.0の仕様で定義されていません。

よって、Implicit Flowで受け取ったアクセストークンを用いてプロフィールAPIをたたき、その結果をユーザー認証に利用するサービスはアクセストークン置き換え攻撃により別のユーザーのログイン状態を生成されるリスクが存在することになります。

誰が攻撃者になれるのか

アクセストークンを扱える存在であれば、トークン置き換え攻撃が可能です。

  • Implicit Flowを利用有無にかかわらず、アクセストークンを扱っているクライアントの管理人
  • Implicit FlowのリダイレクトURIの履歴にアクセスでき、他人の上記条件のアクセストークンを取得できる人物

プロフィールAPIにアクセスさせる必要があるため、攻撃対象のクライアントからのリクエストと同じもしくはより広いScopeを持ったアクセストークンが必要です。

対策

Authorization Code Flowを利用

まず初めに、現在Implicit Flowを利用しているクライアントの開発者は、Authorization Code Flowを利用できないかを確認していただければと思います。

Authorization Code Flowを使えそうな例として、iOS/AndroidアプリとバックエンドのWebサーバー間でアプリ固有のデータをやりとりするような場合です。

アプリ上で認可サーバーとのやり取りを行わずにAuthorization CodeをバックエンドのWebサーバーに送り、Webサーバー側でアクセストークンの取得処理を実装することができれば、Implicit Flowのリスクを回避することができます。

アクセストークンの内容を確認できるAPIを利用

自分が利用している認可サーバーが"アクセストークンが発行された対象のクライアントなどの情報を確認できるAPI"を持っていれば、アクセストークン置き換え攻撃を検知可能でしょう。

OAuth.jpの記事によると、Facebookでは発行元のクライアントを取得することが可能なようです。

「OAuth 2.0 (Implicit Flow) でログイン」の被害例 - OAuth.jp

ただし、OAuth 2.0にこのような機能を持つエンドポイントや処理の定義はありませんので、利用するためには認可サーバー側で独自に実装を行う必要があります。

OpenID ConnectのID Tokenを利用

まだ実際のプロダクション環境でOpenID Connectを実装しているサービスはありませんが、認可サーバーがもし対応した場合にはID Tokenを利用することによりアクセストークン置き換え攻撃に対応することが可能です。

OpenID ConnectのID Tokenについての仕様は"Messages"の"2.1.1. ID Token"にあります。

http://openid.net/specs/openid-connect-messages-1_0.html#id_token : "2.1.1. ID Token"

サーバー、クライアント、ユーザー識別子、リプレイアタック防止のための検証に利用できる文字列が含まれたJSONを文字列として表し、署名がつけられて渡されます(JWT,JWSという仕様です)。

  • iss : Server Identifier
  • user_id :
  • aud : Client Identifier
  • exp : (有効期限)
  • acr : 認証レベルなど
  • nonce : リプレイアタック防ぐための文字列。Authorization Requestにnonceパラメータが必須で追加される

OAuth 2.0の仕様に与える影響を抑えるため、ID Tokenとはアクセストークンとは別に提供され、クライアントはリソースアクセスに利用しません。

このID Tokenをクライアントが適切に検証し、ユーザー認証のロジックに利用することで、アクセストークン置き換えによるなりすましのリスクを防ぐことが可能です。

クライアントのID Tokenの処理

ID Tokenは署名つきのJSONオブジェクトですので、署名検証を行って有効性が確認できた場合はJSONオブジェクトを複合して利用可能です。

また、それらの検証をサーバーに任せられるCheck ID Endpointと呼ばれるAPIにID Tokenを送ることで内容が返されます。

クライアントは、下記のようなJSONオブジェクトの内容の検証を実装する必要があります。

  • aud : 自らのクライアント識別子であることを確認
  • iss : サーバーが意図したものであることを確認
  • exp : 有効であることを確認
  • nonce : Authorization Requestに含んだ文字列と等しいことを確認

OpenID ConnectのID Tokenの検証についての記述はこちらにあります。

http://openid.net/specs/openid-connect-messages-1_0.html#anchor23 : "5.4. Check ID Response Verification"

クライアント側のアクセストークンとの組み合わせの検証について

現状のID Tokenにはアクセストークンの値が含まれません。そのため、厳密なアクセストークン置き換えの検知は不可能です。

Connect仕様策定メンバーの中でID Tokenにアクセストークンのハッシュ値を含むことを検討しています。

まとめ

  • OAuth 2.0 Implicit Flowをユーザー認証に利用する際に、アクセストークン置き換えによる別ユーザーへのなりすましリスクが存在する
  • 同じ認可サーバーに対して発行され、同じもしくは広いScopeを付加されたアクセストークンを取得できれば攻撃者になりえる
  • Authorization Code Flowを利用可能な場合は利用するよう修正する
  • アクセストークンの内容が確認できるAPIが提供されている場合はそれを利用する
  • 認可サーバー、クライアントが互いにOpenID ConnectのID Tokenの発行/チェックロジックを実装することでも対応可能

私はOpenID Connectを推していますので最後の方法をお勧めさせていただきます。

デモが見たい場合

先週末にIdentity ConferenceというIdentityやセキュリティに詳しい人間が集まる勉強会がありました。

そこで軽い気持ちで緩い感じのデモをしたところ、

  • 「OpenID Connectを啓蒙していく立場として、実装面で気をつけることをより細かく説明するよう気をつけるべき」
  • 「こんな説明ではわからない。しっかり問題となるユースケースとリスクを整理して対応方法を紹介・・・」

という旨の指摘をいただきましたので、今回のエントリは自分なりに仕様の言及箇所など整理しなおしたものです。

idcon #11でのデモ用スライドはこちらになります。

Idcon11 implicit demo

これだけではわかりませんが、Implicit Flowを実装したクライアント×2、OpenID ConnectのID Tokenの処理を実装しているクライアント×1を用意しました。

コピペで手が震えたのか、デモにはかなり苦戦しましたが。。。

ではまた!

2012/01/17

GoogleQRコードログインのしくみを勝手に想像する

こんばんは、ritouです。

GoogleがQRコードを用いたログインを実装していたという記事を見つけました。

この機能について勝手にしくみを想像し、問題と解決案を提案します。

概要

上記リンクにも説明があります。

  1. 未ログイン状態のブラウザで、QRコードを表示するURLにアクセスする
  2. 表示されたURLをiPhone/Android/BrackBerryのバーコードリーダーアプリで読み込む。iPhone/Android/BrackBerryでブラウザを起動してそのURLを開く
  3. その画面からGmailまたはiGoogleをはじめるボタンをクリックする
  4. QRコードが表示されていたブラウザがいつのまにかログイン状態になっている

こんな感じで、ブラウザ側に一切Email/PWを入力せずにログインが可能になるというしくみです。

しかし、1/17 22時時点ではこんな文言が表示されて何もできなくなりました。

Hi there - thanks for your interest in our phone-based login experiment.

While we have concluded this particular experiment, we constantly experiment with new and more secure authentication mechanisms.

Stay tuned for something even better!

Dirk Balfanz, Google Security Team.

phone-based login experimentと呼ばれるらしいですね。

ここからは昼間に試した記憶を頼りに進めます。

何が行われているか

それでは、どんなことが行われているのかを想像します。

  1. QRコードを表示するURLは、サーバー側からのPush通知を待つ(もしくはサーバー側にポーリング処理を行う)
  2. QRコードには、なんらかの識別子が含まれる
  3. ユーザーが同意すると、QRコードを特定する識別子とユーザー情報がサーバーに送られる
  4. サーバーがその識別子にマッチするブラウザにユーザー情報を含んだPush通知を送る or ブラウザ側からの通信にユーザー情報を含んだレスポンスを返し、ログイン状態になる

厳密なシーケンスではないですが、図にしてみました。

f:id:ritou:20120117224317p:image

こういうフロー、どこかで見たことがあります。

OAuth 1.0

長くなる気配なので、結論からいきます。これは、

「信頼の置けないブラウザ上でEmail/PWを入力させずに済むようにSmartphoneによるOAuth 1.0を自サービスに適用したしくみ」

と言えます。

GoogleにログインするだけなのにOAuth、しかも1.0なんて何を言ってるんだと思われるかもしれませんが、ちょっと見方を変えてみましょう。

ブラウザを一昔前のTVだと思ってください。文字入力できるけどブラウザ積んでるわけではない場合を想像してください。

  1. TV画面にQRコードが表示される(ServerからRequst Tokenを取得してURLに含む)
  2. ケータイのカメラでQRコードを読み込む。ケータイのブラウザが立ち上がる
  3. OAuthの同意画面で同意する。ついでにPINを表示される。(Request TokenにUser情報が紐づく)
  4. PINを入力すると、TV画面でログイン状態になっている(Request TokenからAccess Tokenを取得、紐づいたユーザー情報を取得してログイン状態にできる)

PINについて気になってるかもしれませんが、後で説明するので待っててください。

2009年12月にYahoo! JAPANのTech Blogでこんな記事がありました。

モバイル版OAuthの便利な活用方法 (Yahoo! JAPAN Tech Blog)

たいたいここにある例と同じですよね。

ただし、TVの例に出てくるPIN入力が存在しません。

これは、2009年4月に脆弱性が発覚したOAuth 1.0の仕様とほぼ同じです。

PINを持たないこの実装では、QRコードを他人に読ませてStartリンクを押させることで手元のブラウザを他人のアカウントでログイン状態にすることが可能です。

OAuth 1.0の問題についての過去エントリを書いていました。OAuth Security Issue - r-weblife

手順は増えてしまうけどOAuth 1.0a仕様にすれば実用的かも

上記のとおりリスクがあるわけなので、私がG社の社員であれば1.0a仕様にアップグレードすることを提案します。

PC Browser : QRコード表示と同時に、PIN入力欄を表示

Smartphone : Start with Gmailをクリックした後に、PINを表示してユーザーのPC Browserに入力させる

というのはいかがでしょうか?

OAuth 2.0の仕様しか知らない人は、PC BrowserがClient, "PIN"をAuthorization Codeと読み替えてAuthorization Code Flowをやるってことだと覚えてください。

いずれにしろ、このような使い方で旅先や公共のPCでキーロガーなどを防ぐ方法は個人的にはアリだと思います。このphone-based loginの今後に注目です。

「いえーい G社員 見てるー?」

ではまた。

2012/01/06

OAuth 2.0時代の署名つきリクエストとは

こんばんは、ritouです。

前回のmixiページアプリについてのエントリでOAuth 1.0の署名つきリクエストが使われていることに触れました。mixi Graph APIはOAuth 2.0対応してるのに、、、と少しモヤモヤしたので、まずはOpenSocialの新しいSpecのSigned Fetchの部分を見てみました。

OpenSocial Core Gadget Specification 2.0.1

OpenSocialでは様々なリクエストが存在しますが、OAuth 2.0をサポートする宣言の通り、gadgets.io.AuthorizationTypeが"OAUTH2"の場合の定義が追加されています。ただし、"SIGNED"、つまり署名付きリクエストの部分はOAuth 1.0系のままでした。感覚的にはContainer側が両方サポートするのはしょうがないとしても、開発者がOAuth 1.0の署名検証をずっと実装し続ける必要があるのはちょっとつらいのかなとも思いました。

というわけで、OpenSocialの話はいったんおいておいて、今回はOAuth 2.0時代に署名付きリクエスト送ろうと思ったらどうすればいいのか?について調べました。

Client CredentialでAccess TokenとってMAC Authenticationを使ってリクエストに署名つける方法もありますが、まぁそれもちょっと大変ですし。

最近だと、Facebookの独自実装とOpenID Connectで似て非なる署名付きのリクエストというかパラメータ自体に署名をつけて送る方法が使われているので紹介します。

FacebookのSigned Request

使われどころはこんな感じですね。

  • アプリがfacebookの環境からロードされたとき
  • ユーザーがアプリを登録/削除したとき

このとき、以下のようなsigned_requestパラメータが指定されます。

vlXgu64BQGFSQrY0ZcJBZASMvYvTHu9GQ0YM9rjPSso
.
eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsIjAiOiJwYXlsb2FkIn0

見やすいように改行されていますが、"."より前はSignature,後はBase64 URL EncodeされたJSONデータになります。

JSONデータの中身はこちら。

  • user
  • algorithm
  • issued_at
  • user_id
  • oauth_token
  • expires
  • app_data
  • page

サンプルではalgorithmがHMAC-SHA256で、Keyにはapp secret(client secret)が利用されているようです。RSAでSignature作りたい場合などはどうするのでしょうか・・・

次に、OpenID Connectで使われているJWTを見てみましょう。

OpenID Connect : JWT(JWS)を用いた署名つきパラメータ

OpenID Connectでは、通常のOAuth 2.0のAuthorization RequestにOpenID Connect独自のパラメータなどを追加するためにちょっと拡張します。その際に、リクエストの内容をJSONオブジェクトにして署名をつけた文字列を作成し、"request"パラメータとして送っています。

その時にJSONオブジェクトから作成された文字列をJWTと呼び、以下の仕様で定義されています。

バージョンが古いJWSを説明したエントリはこちらです。→ JSON Web Signatureの紹介 - r-weblife

ちょっと端折りますが、内容はこんな感じです。

  • Header, Payload, Signatureの3つで構成
  • HeaderはalgなどSignatureに必要な値が入ったJSONデータをBase64 URL Encodeしたもの
  • Payloadは送りたいJSONデータをBase64 URL Encodeしたもの
  • SignatureはHeaderで指定した内容で生成した署名をBase64 URL Encodeしたもの

似てますよね。自分がこっちよりの立場なこともありますが、facebookのやり方よりもこっちの方が汎用的な気がします(そうなるように設計されています)。

mixiのPlatformのようにOpenSocialに準拠しているようなところは別ですが、OAuth 2.0でAPIを提供しているようなところが署名付きのリクエストをやりとりしたいなーと思った場合はこちらのJWSを用いるのはいかがでしょうか?

このあたりは引き続きウォッチしていきたいと思います。

ではまた。

2012/01/05

mixiページアプリとOAuthの関係

こんばんは、ritouです。

今年もがんばりましょう。

mixiページアプリをご存知でしょうか?

上記リンクを見た限り、こんな感じでしょうか。

  • mixiページ内でiframe使って表示
  • Page/pageApp APIやそれ以外のMixi Graph APIも利用できる
  • 公開申請して通ればいろんなmixiページにあなたのmixiページアプリが使われるかも!

このmixiページアプリを作ってみたところ、みんな大好きなOAuthがふんだんに使われていますので今回はしくみとOAuthの使われどころを紹介します。

試しに作ってみた

とりあえず何か作りたいなと思ったので、簡単な拡散用のページアプリを作成してみました。その名も「拡散希望」。

  • mixiページ名とURLを取得
  • mixiボイスを投稿することで友人に拡散
  • Facebookのいいね!やTwitterに書き込むことでmixiの外にも拡散

開発中のステータスにあるmixiページアプリは、自分が管理しているページにしか入れられず、しかも管理者しか利用できない(見えない)ので、URLをさらしてもどうにもなりません。

最初にmixiページアプリを選択して起動したときの画面はこちら。

f:id:ritou:20120105000217p:image:w360

ここでやっていることを紹介します。

起動時 : 署名付きリクエストを処理

起動時にOAuth 1.0系の署名付きのリクエストが送られます。mixiアプリと同じですね。

署名付きリクエストの検証 << mixi Developer Center (ミクシィ デベロッパーセンター)を参考にして、OAuth::Lite::ServerUtilを用いて検証しました。

このOAuth::Lite、便利ですね。

PC用とスマートフォン用でRSAの公開鍵が違うのでxoauth_signature_publickeyパラメータなどで判定するのがよさそうです。

sub validate {
    my $req = shift;

    if ( $req->param('xoauth_signature_publickey') and $req->param('oauth_signature') ) {

        my $method = $req->method;
        my $url = $req->uri;
        my $params = $req->params;

        my $util = OAuth::Lite::ServerUtil->new();
        $util->support_signature_method('RSA_SHA1');

        my $ca = ( substr($req->param('xoauth_signature_publickey'), 0, 7) eq 'page_pc' ) ? $mixi_ca_pc : $mixi_ca_touch;
        $public_key = Crypt::OpenSSL::CA::X509->parse($ca)
                      ->get_public_key()
                      ->to_PEM();

        my $ret = $util->verify_signature(
                      method          => $method,
                      url             => $url,
                      params          => $params,
                      consumer_secret => $public_key,
                  );
        return $ret;
    }else{
        return 0;
    }
}

githubソース : lib/MixiPageApp1/OAuthUtil/SignedRequest.pm at master from ritou/p5_MixiPageApp1_sample - GitHub

署名検証後 : mixiページの情報を取得

mixiページから送られてたリクエストだということが確認できた後は、ページの情報を取得します。

ユーザーの認可が不要な、いわゆる2leggedな処理になります。

このとき、OAuth 2.0のClient Credentialsの仕様に沿ってAccess Tokenを取得して、Page APIにアクセスする必要があります。

OAuth::Lite2::Client::ClientCredentialsを使うと簡単です。

このOAuth::Lite2、便利ですね。作った人すごいですね!

sub get_from_clientcredentials {
    my $config = shift;

    my $client = OAuth::Lite2::Client::ClientCredentials->new(
        id               => $config->{client_id},
        secret           => $config->{client_secret},
        access_token_uri => q{https://secure.mixi-platform.com/2/token} 
    );

    my $access_token = $client->get_access_token() or return '';

    return $access_token;
}

githubソース : lib/MixiPageApp1/OAuthUtil/AccessToken.pm at master from ritou/p5_MixiPageApp1_sample - GitHub

あとはリソースアクセスです。

ページ基本情報を取得するAPIの仕様はこちら : Page API << mixi Developer Center (ミクシィ デベロッパーセンター)

sub get_page_info{
    my ($access_token, $page_id) = @_;

    # build request
    my $endpoint = sprintf(q{https://api.mixi-platform.com/2/pages/%s}, $page_id);
    my $req = HTTP::Request->new( GET => $endpoint );
    $req->header( Authorization => sprintf(q{OAuth %s}, $access_token) );

    # get response
    my $res = LWP::UserAgent->new->request($req);
    return decode_json($res->content);
}

githubソース : lib/MixiPageApp1/OAuthUtil/Resource.pm at master from ritou/p5_MixiPageApp1_sample - GitHub

レスポンスからidとdisplayNameを取り出せば拡散用のURLが作れそうです。あとはテンプレートに渡してFacebookやTwitterのJSをごにょごにょすれば外への拡散リンクが作成できますね。

ボイス投稿準備その1 : ユーザーに対してプロフィールデータ取得とボイス投稿の認可を要求

プロフィール情報の取得とボイス投稿をするためには、OAuth 2.0の認可要求が必要です。

これはJavaScriptが用意されているのですが、mixiページを表示したらみんなに強制的にPermisionくれくれするのもあれかなと思い、ボタン押したら同意画面出てくるようにしました。

f:id:ritou:20120105000218p:image:w360

いい感じで認可画面が表示されます。

JavaScriptの仕様 : JavaScript API << mixi Developer Center (ミクシィ デベロッパーセンター)

githubソース : root/base/Root.tmpl at master from ritou/p5_MixiPageApp1_sample - GitHub

ボイス投稿準備その2 : OAuth 2.0 Webserver Flowのcallback処理したらAPIアクセスの準備完了

戻り先を用意しておきます。(/Authorization/callbackとか)

ここではOAuth::Lite2::Client::WebServerを使ってAccess Tokenの取得まで行います。

このOAuth::Lite2、便利ですね。作った人すごいですね!こういう人の隣の席とかで働けたら幸せですね!

sub get_from_authorizationcode {
    my ($config, $code) = @_;

    my $client = OAuth::Lite2::Client::WebServer->new(
        id               => $config->{client_id},
        secret           => $config->{client_secret},
        authorize_uri    => q{https://mixi.jp/connect_authorize.pl},
        access_token_uri => q{https://secure.mixi-platform.com/2/token} 
    );

    my $access_token = $client->get_access_token(
        code         => $code,
        redirect_uri => $config->{redirect_uri}
    ) or return '';

    return $access_token;
}

githubソース : lib/MixiPageApp1/OAuthUtil/AccessToken.pm at master from ritou/p5_MixiPageApp1_sample - GitHub

f:id:ritou:20120105000219p:image:w360

あとはボイスへの投稿ですね。

sub post_voice{
    my ($access_token, $message) = @_;

    # build request
    my $endpoint = q{https://api.mixi-platform.com/2/voice/statuses/update};
    my $req = HTTP::Request->new( POST => $endpoint );
    $req->header( Authorization => sprintf(q{OAuth %s}, $access_token) );
    $req->content_type('application/x-www-form-urlencoded');
    $req->content('status='.uri_escape($message));
    	
    # get response
    my $res = LWP::UserAgent->new->request($req);
    return decode_json($res->content);
}

githubソース : lib/MixiPageApp1/OAuthUtil/Resource.pm at master from ritou/p5_MixiPageApp1_sample - GitHub

こんな感じでOAuthに絡む部分の説明は終わりです。

あとは、1つのmixiページ内に複数のページアプリが使えたりしますし、複数ページでページアプリが使われることもあるので、セッション周りはうまく考えて作りこみ必要がありますね。

まとめ

上記の通り、OAuth 1.0系とOAuth 2.0系の共演になっています。

悪名高きOAuth 1.0系の署名ではありますが、OAuth 2.0系でリソースアクセス以外の署名付きリクエストが定義されていないのでしょうがないですね。

まぁ、1.0系のほうはmixiページ作られたことのある開発者の方は慣れていると思うので問題ないでしょう。

ずっとPHP触ってたので簡単なスクリプト意外、Perl書くの久しぶりというかほぼ初めてでしたが、とりあえず動かしてみたところ、「OAuthの処理さえおさえておけばページアプリ作るのそんなに難しくなさそう」って思いました。

みなさんもmixiページアプリを作られてみてはいかがでしょうか?

ではまた!