OSSのクライアントインストール型プログラム(Atom用パッケージ)でOAuth2をなるべく安全に利用する

## はじめに

前の記事の、「[配布物にOAuth2で利用するclient_secretを含むことのリスク](http://qiita.com/nobuhito/items/6e2c511fdff27ff1eeda)」 についての続き。

いろいろなケースでも使えるとは思うが、具体的にはAtom用パッケージでGoogleのAPIを利用するためにOAuth2での認証を行う方法になっている。

先日のポストのあと、Twitterでのコメントも合わせて、良く分かってないながらもいろいろ整理してみた。ここらへんが結構わかりやすかった。 → 「[RFCとなった「OAuth 2.0」――その要点は？](http://www.atmarkit.co.jp/ait/articles/1209/10/news105.html)」

まず、公開するプログラムのタイプは2つありそう。

  1. サーバー型のプログラムで利用する前に前準備が必要で対象は不特定多数
  2. クライアント側のプログラムで利用者はほぼ本人のみですぐに使いたい

また、公開方法についても2つある。

  1. バイナリで公開
  1. ソースで公開

今回のパターンはAtomのパッケージなので、2-2に該当するパターンとなる。
例えば、1-2であればサーバー側の処理次第で `client_id` も `redirect_uri` も隠せるだろうし、バイナリであれば中に隠せる(本当に悪用するのが目的の場合は隠し通すのは難しいと思われるが)と思うのであまり問題にはならなそう。

なので、ここではソースで公開するクライアント側のプログラムに的を絞って考えてみることにする。

## 目指すポリシーとそれに対応する望ましいフロー

### ポリシー

ポリシーについては

  1. クライアント側で個人が利用するプログラムなのでできるだけ面倒なことは避けさせたい
  2. とは言っても認証と認可は必要なのでOAuth2を利用する(API_KEYは使えない)
  3. `client_secret.json` はそのままの形で悪用される可能性が強いのでソース同梱は避ける
  4. いざというときのために `client_id` を変えても利用者にはあまり影響がないようにする
  5. 認可の際に確認はできるものの `client_id` が悪用された場合のことを考えて `scope` は変更できないようにする
  6. 取得した `access_token` についてはメモリ内だけで処理してファイルやlocalStorageに残さないようにする

あたりが妥当なところだろうか。

### フロー

これを満たすためには、

  1. Atomの起動時にAtom経由でネット上の認証準備用ページを開く
  2. 認証準備用ページでは予めセットしておいた `redirect_uri` と `scope` を使ってGoogleの認証画面を開く
    AtomとGoogleの組み合わせだと一度認証と認可が行われていればユーザーの操作なしに `access_token` が新規発行されるのでその場合は6へ進む
  3. Googleの認証画面でIDとパスワードを入力して認証作業
  4. Googleの認可画面で利用するプログラム名と `scope` の範囲を確認して認可する
  5. 予めセットしておいた `redirect_uri` へ転送される
  6. webviewの機能を利用して `access_token` を取得する
  7. `access_token` は保存しないで変数を使いまわして期限(3600sec)が来たら1に戻って再取得する

という流れを作れればなんとかクリアできそう。

## 実装内容

上記のポリシーとフローを意識しながら、AtomパッケージでOAuth2認証・認可を行い、取得した `access_token` を用いてAPIを利用するまでをまとめてみる。

### GoogleAPIの設定

まずはフローに合わせた新しい認証情報を作る

  1. [GoogleDevelopersConsole](https://console.developers.google.com/) で新しいプロジェクトを作成
  2. 利用したいAPIをオンにして「ウェブアプリケーション」タイプのクライアントIDを作成する
  3. リダイレクトURIについては、この次に説明するGitHubPagesのアドレスを入れておく   
     例： `http://USER_ID.github.io/PACKAGE_NAME/oauth2callback`

ちなみに、今回作成した `client_secret` は使わない感じ。

### GitHubPagesの作成

初めはGoogleAppsScriptでお手軽に作ろうと目論んでいたが、Googleの認証画面へのリダイレクトっぽい動作を実装できずに断念。
結局はGitHubPagesで [実装](https://github.com/nobuhito/atom-gmail-checker/blob/gh-pages/oauth2callback.html) してみた。結果的にだが、ブランチ違いとはいえ1つのリポジトリで全てが見えるので透明性の確保という点でも良い選択だったと思う。

ソースを見てもらうのが一番早いが、一応簡単に説明すると

  1. GitHubでgh_pagesブランチを作成(GitHubのページ作成から実行すると楽)
  1.  `oauth2callback.html` を作成(名前はなんでもいいんだろうけど、この名前が一般的っぽいので)
  1. [ここ](https://github.com/nobuhito/atom-gmail-checker/blob/gh-pages/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 <webview> tag](https://github.com/atom/electron/blob/master/docs/api/web-view-tag.md) を見ていただくとして、外部から表示させるURLを送ったり、ロード後の状態を取得したりも出来る。

これを元にしてログインページを実装したのが、 [atom-gmail-checker-auth-view.coffee](https://github.com/nobuhito/atom-gmail-checker/blob/master/lib/atom-gmail-checker-auth-view.coffee) になる。
ちょっとCloseボタンの表示がイケてないが、これくらいの実装でも今回のフローにあった内容の処理が実現できる。

そしてこれをログインの時に表示させればいいのだが、今回は右側に新しいパネルを作成してそこに表示させている。
本当はモダールで表示させたかったのだがテーマで幅が決まってるらしく、ハックが面倒なので諦めた。

また、 `access_token` の他に `Expires_in` も取り出し、タイマーかけて `access_token` は再取得している。
ここらへんはReflesh_tokenがどうのこうのとかありそうだけど、 [Using OAuth 2.0 for Client-side Applications](https://developers.google.com/identity/protocols/OAuth2UserAgent) 
を見ても `reflesh_token` なる文字が出てこないので、これはこういうものだと思って都度取得にしている。

あとは取得した `access_token` は変数だけで利用するようにしてAPIを叩けば、`client_secret.json` 無しで無事にデータが取得できる。

ここで実装したログインページはこんな感じになる。
![atom-oauth2.gif](https://qiita-image-store.s3.amazonaws.com/0/15745/161a4eeb-57d5-7708-5d7f-fb73e433600c.gif)

## ポリシーの検証

ここでポリシーを検証してみる。

### 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チェッカー](https://atom.io/packages/atom-gmail-checker) の新バージョンをリリースしました。
今回の修正で安心してお使いいただけますので、よろしくお願いします。


ってか、なげーよｗｗ