はじめに
前の記事の、「配布物にOAuth2で利用するclient_secretを含むことのリスク」 についての続き。
いろいろなケースでも使えるとは思うが、具体的にはAtom用パッケージでGoogleのAPIを利用するためにOAuth2での認証を行う方法になっている。
先日のポストのあと、Twitterでのコメントも合わせて、良く分かってないながらもいろいろ整理してみた。ここらへんが結構わかりやすかった。 → 「RFCとなった「OAuth 2.0」――その要点は?」
まず、公開するプログラムのタイプは2つありそう。
- サーバー型のプログラムで利用する前に前準備が必要で対象は不特定多数
- クライアント側のプログラムで利用者はほぼ本人のみですぐに使いたい
また、公開方法についても2つある。
- バイナリで公開
- ソースで公開
今回のパターンはAtomのパッケージなので、2-2に該当するパターンとなる。
例えば、1-2であればサーバー側の処理次第で client_id
も redirect_uri
も隠せるだろうし、バイナリであれば中に隠せる(本当に悪用するのが目的の場合は隠し通すのは難しいと思われるが)と思うのであまり問題にはならなそう。
なので、ここではソースで公開するクライアント側のプログラムに的を絞って考えてみることにする。
目指すポリシーとそれに対応する望ましいフロー
ポリシー
ポリシーについては
- クライアント側で個人が利用するプログラムなのでできるだけ面倒なことは避けさせたい
- とは言っても認証と認可は必要なのでOAuth2を利用する(API_KEYは使えない)
-
client_secret.json
はそのままの形で悪用される可能性が強いのでソース同梱は避ける - いざというときのために
client_id
を変えても利用者にはあまり影響がないようにする - 認可の際に確認はできるものの
client_id
が悪用された場合のことを考えてscope
は変更できないようにする - 取得した
access_token
についてはメモリ内だけで処理してファイルやlocalStorageに残さないようにする
あたりが妥当なところだろうか。
フロー
これを満たすためには、
- Atomの起動時にAtom経由でネット上の認証準備用ページを開く
- 認証準備用ページでは予めセットしておいた
redirect_uri
とscope
を使ってGoogleの認証画面を開く
AtomとGoogleの組み合わせだと一度認証と認可が行われていればユーザーの操作なしにaccess_token
が新規発行されるのでその場合は6へ進む - Googleの認証画面でIDとパスワードを入力して認証作業
- Googleの認可画面で利用するプログラム名と
scope
の範囲を確認して認可する - 予めセットしておいた
redirect_uri
へ転送される - webviewの機能を利用して
access_token
を取得する -
access_token
は保存しないで変数を使いまわして期限(3600sec)が来たら1に戻って再取得する
という流れを作れればなんとかクリアできそう。
実装内容
上記のポリシーとフローを意識しながら、AtomパッケージでOAuth2認証・認可を行い、取得した access_token
を用いてAPIを利用するまでをまとめてみる。
GoogleAPIの設定
まずはフローに合わせた新しい認証情報を作る
- GoogleDevelopersConsole で新しいプロジェクトを作成
- 利用したいAPIをオンにして「ウェブアプリケーション」タイプのクライアントIDを作成する
- リダイレクトURIについては、この次に説明するGitHubPagesのアドレスを入れておく
例:http://USER_ID.github.io/PACKAGE_NAME/oauth2callback
ちなみに、今回作成した client_secret
は使わない感じ。
GitHubPagesの作成
初めはGoogleAppsScriptでお手軽に作ろうと目論んでいたが、Googleの認証画面へのリダイレクトっぽい動作を実装できずに断念。
結局はGitHubPagesで 実装 してみた。結果的にだが、ブランチ違いとはいえ1つのリポジトリで全てが見えるので透明性の確保という点でも良い選択だったと思う。
ソースを見てもらうのが一番早いが、一応簡単に説明すると
- GitHubでgh_pagesブランチを作成(GitHubのページ作成から実行すると楽)
-
oauth2callback.html
を作成(名前はなんでもいいんだろうけど、この名前が一般的っぽいので) - ここ を参考にしてHTMLを作成
という感じ。
上記だけであればWebブラウザ上だけで行える。ファイルとかディレクトリとかまですべてキレイにしたい場合はGitクライアントでの操作が必要だけど。
そして oauth2callback.html
の中身については、内部的にはGoogle認証ページヘの誘導部分とGoogle認証ページからの戻りの部分に分かれている。
Google認証ページへの誘導部分は、任意のURLのハッシュ(今回は #auth
)の時に、GoogleAPIに登録した client_id
と redirect_uri
、その後に利用する scope
をつけて、Googleの認証ページヘリダイレクトさせてる。これで client_id
をパクられてもスコープは固定に出来る。
他にGoogleAnalysticsのスクリプトもつけているが、ここらへんはお好みで。
次にGoogle認証ページからの戻りの部分では、単に認証が成功したとのメッセージを表示させるだけ。
URLのハッシュ部分に access_token
がついているのでここでは何もしなくとも良い。
この段階で redirect_uri#auth
にアクセスすると認証画面が表示され、IDとPASSWDを入れると認可画面が出て、許可した後に 「Authorization succeeded for PACKAGE_NAME.」 が表示された画面に遷移すれば認証・認可フローは完成となる。
Atomパッケージへログインページの実装
(これ以降はAtomパッケージに限った話になります)
あとは、認証準備用ページのリダイレクト用JSが処理できて、各種HTMLを処理できて、最後はGoogleから送られてくるURLハッシュを取得できるログインページを実装する必要がある。
そういうことが出来るのか探したところ、AtomというかElectronには(にも?) webview
というカスタムタグがあることを発見(これを発見したからこのページの実装を思いついたわけだが)。
詳細は The tag を見ていただくとして、外部から表示させるURLを送ったり、ロード後の状態を取得したりも出来る。
これを元にしてログインページを実装したのが、 atom-gmail-checker-auth-view.coffee になる。
ちょっとCloseボタンの表示がイケてないが、これくらいの実装でも今回のフローにあった内容の処理が実現できる。
そしてこれをログインの時に表示させればいいのだが、今回は右側に新しいパネルを作成してそこに表示させている。
本当はモダールで表示させたかったのだがテーマで幅が決まってるらしく、ハックが面倒なので諦めた。
また、 access_token
の他に Expires_in
も取り出し、タイマーかけて access_token
は再取得している。
ここらへんはReflesh_tokenがどうのこうのとかありそうだけど、 Using OAuth 2.0 for Client-side Applications
を見ても reflesh_token
なる文字が出てこないので、これはこういうものだと思って都度取得にしている。
あとは取得した access_token
は変数だけで利用するようにしてAPIを叩けば、client_secret.json
無しで無事にデータが取得できる。
ポリシーの検証
ここでポリシーを検証してみる。
1. クライアント側で個人が利用するプログラムなのでできるだけ面倒なことは避けさせたい
利用者に client_secret.json
や client_id
を用意させること無く、表示された認証画面で認証し、認可画面で許可ボタンをクリックするするだけで利用できている。
2. とは言っても認証と認可は必要なのでOAuth2を利用する(API_KEYは使えない)
API_KEYなどは利用しないで、OAuth2を利用している。
3. client_secret.json
はそのままの形で悪用される可能性が強いのでソース同梱は避ける
client_secret.json
は利用せず、サーバー側に設定。
4. いざというときのために client_id
を変えても利用者にはあまり影響がないようにする
もし client_id
を変更するようなことがあっても、GitHubPagesの設定を変更するだけでOK。
5. 認可の際に確認はできるものの client_id
が悪用された場合のことを考えてSCOPEは変更できないようにする
client_id
に紐づく redirect_uri
のサーバー側で設定されているので、SCOPEを変更することは出来ない。
6. 取得した access_token
についてはメモリ内だけで処理してファイルやlocalStorageに残さないようにする
パッケージ側の実装にもよるが、 access_token
が失効したら再取得する方法であればファイル等に保存する必要が無い。
ということで、先に設定したポリシーについてはすべてクリアできている。
最後に
てっとり早く作れるAtomのパッケージだが、あまり深く考えずに作るとセキュリティ上問題が発生する恐れもある(未だに以前の方法だと本当にセキュリティ上問題があるのかがよく分かってないが)。
かと言って「セキュリティって考えるの面倒だよね。面倒だからこの機能実装するのやーめよっと」とならずに、これがAtomのパッケージにかぎらずクライアント側のOSSでOAuth2を利用する人達の参考になれば幸いです。
今回外人さんからメールをもらって改めて考えてみたのだが、結果的なことよりもOAuth2自体の勉強もできたしWebviewの存在を知ったことが良かったと思う。
ということで、 Atom用Gmailチェッカー の新バージョンをリリースしました。
今回の修正で安心してお使いいただけますので、よろしくお願いします。
ってか、なげーよww