ElectronでのOAuth

Electronからtwitter APIを叩こうとして少しハマったのでメモ.
ElectronにおけるOAuthのログイン処理関連の話なので, 別にTwitterだけに限った話ではないはず。

## ハマった内容
遭遇した問題としては, ElectronからTwitterのAPIを叩こうとしたのだけど, oauth\_verifierが上手く取得できない, という内容だ.

最初, oauthのcallbackに `'file://' + __dirname + 'redirect.html'`のようにファイルスキーマのURLを指定してみたのだけど, 下記のようにエラーが表示されてしまった.

```
Not allowed to load local resource: file:///(中略)/redirect.html?oauth_token=...&oauth_verifier=...
```

ログから, oauth\_verifierの取得自体は成功しているもの, 画面遷移でコケているのは明らかだ.
通常, Electronのアプリでは, rendererProcess中でlocation.hrefを参照するとファイルスキーマURLが返ってくるので, 動くのかなと思ったけど, どうもダメな様子.

~~[issue#483](https://github.com/atom/electron/issues/483)を見ると, 「`BrowserWindow`作成時に, `{'web-preferences': {'web-security': false}}`にすれば出来るよ」みたいな記載もあったので試してみたけど, 特に改善されず.~~
*2016.03.21追記*
```{webPreferences: {webSecurity: false}}``` とすることで, 上記のエラーを抑止し、ファイルスキーマURLへリダイレクト可能となるという情報をコメントにて頂きました. 

## 解決策
最終的に, 下記のようにすることでトークンの取得が出来るようになった.

```js:main.js
const BrowserWindow = require('app').BrowserWindow;
const twitterAPI = require('node-twitter-api');

const twitter = new twitterAPI({
  consumerKey: /* コンシューマキー */,
  consumerSecret: /* コンシューマシークレット */,
  callback: 'https://www.google.co.jp/' // 別にどこでもよい
});

twitter.getRequestToken((error, requestToken, requestTokenSecret) => {
  const url = twitter.getAuthUrl(requestToken);
  const loginWindow = new BrowserWindow({width: 800, height: 600});
  loginWindow.webContents.on('will-navigate', (event, url) => {
    // https://www.google.co.jp/?oauth_token=...&oauth_verifier=...のようなURLが渡ってくる.
    var matched;
    if(matched = url.match(/\?oauth_token=([^&]*)&oauth_verifier=([^&]*)/)) {
      twitter.getAccessToken(requestToken, requestTokenSecret, matched[2], (error, accessToken, accessTokenSecret) => {
        console.log('accessToken', accessToken);
        console.log('accessTokenSecret', accessTokenSecret);
      });
    }
    event.preventDefault();
    loginWindow.close();
  });
  loginWindow.loadURL(url);
});
```

ポイントは下記の2点:

* **コールバックのURLは, `http://` or `https://`から始まっていればどこでもよい.**
* **BrowserWindow.WebContentsの`will-navigate`イベントで画面遷移(URL変更)のイベントをフックして, クエリストリングからトークンが取得できる**

## そもそも方向性が合っているのか？(2016.08.25 追記)

このエントリを上げてから1年以上経って、今更の振り返りではあるものの、Electron + OAuthでユーザの認証画面を表示するのにBrowserWindowを利用する事自体、筋が良くないと思うようになった。

その理由は至極単純で、Twitterのパスワードを入力する画面を、アプリケーション(Electron)側に持つとユーザーに「このアプリが悪意を持っていたとしたら、自分のパスワードが抜かれてしまうのでは」という危惧を与えることに繋がるからだ。Twitterで言うのであれば、[PIN-based OAuth](https://dev.twitter.com/oauth/pin-based)を利用した方が良い。

```js
const { OAuth } = require("oauth");
const { app, BrowserWindow, shell, ipcMain} = require("electron");

let win;
const schema = "electron";
app.on("ready", () => {
  win = new BrowserWindow();
  const oauth = new OAuth(
    "https://api.twitter.com/oauth/request_token",
    "https://api.twitter.com/oauth/access_token",
    "your consumer key",
    "your consumer secret",
    "1.0A",
    null,
    "HMAC-SHA1"
  );

  oauth.getOAuthRequestToken((error, oauthToken, oauthTokenSecret, results) => {
    if (error) return;
    const authUrl = `https://api.twitter.com/oauth/authorize?oauth_token=${oauthToken}`;
    // デフォルトブラウザに認証画面を表示
    shell.openExternal(authUrl);

    // PINコード入力画面を表示
    win.loadURL(`file://${__dirname}/pinBased.html`);

    // 入力したPINコードはIPC通信でRenderer -> Main に受け渡す
    ipcMain.once("SEND_PIN", (e, args) => {
      const oauthVerifier = args.pin;
      console.log(oauthToken, oauthVerifier);
      oauth.getOAuthAccessToken(oauthToken, oauthTokenSecret, oauthVerifier, (error, accessToken, accessTokenSecret) => {
        console.log('accessToken', accessToken);
        console.log('accessTokenSecret', accessTokenSecret);
      });
    });
  });

});
```