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を見ると, 「BrowserWindow
作成時に, {'web-preferences': {'web-security': false}}
にすれば出来るよ」みたいな記載もあったので試してみたけど, 特に改善されず.
2016.03.21追記
{webPreferences: {webSecurity: false}}
とすることで, 上記のエラーを抑止し、ファイルスキーマURLへリダイレクト可能となるという情報をコメントにて頂きました.
解決策
最終的に, 下記のようにすることでトークンの取得が出来るようになった.
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://
orhttps://
から始まっていればどこでもよい. - BrowserWindow.WebContentsの
will-navigate
イベントで画面遷移(URL変更)のイベントをフックして, クエリストリングからトークンが取得できる
そもそも方向性が合っているのか?(2016.08.25 追記)
このエントリを上げてから1年以上経って、今更の振り返りではあるものの、Electron + OAuthでユーザの認証画面を表示するのにBrowserWindowを利用する事自体、筋が良くないと思うようになった。
その理由は至極単純で、Twitterのパスワードを入力する画面を、アプリケーション(Electron)側に持つとユーザーに「このアプリが悪意を持っていたとしたら、自分のパスワードが抜かれてしまうのでは」という危惧を与えることに繋がるからだ。Twitterで言うのであれば、PIN-based OAuthを利用した方が良い。
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);
});
});
});
});