VSCode 拡張機能開発で OAuth 認証 - 2
力技で Access Token の取得までやってやりました!!ヾ(:3ノシヾ)ノシ
やったこと
最初はaxios.post
でログイン処理をしようとしましたが、どうしてもうまくいかなかったのでrequest
に変えました。axios
でログイン処理する方法募集です。
コード
HatenaBlogCode/HatenaBlogUtil.ts at master · Hide-KC/HatenaBlogCode · GitHub
言語は TypeScript です。非同期処理のコールバックの中でさらに非同期処理をつないでいるので、あほみたいにネストされてます。今後処理ごとに切り分けよう(´・ω・`)
import * as api from './APIValues'; import * as OAuth from 'oauth'; import axios from 'axios'; import * as request from 'request'; export class HatenaBlogUtil { startOAuth = async () => { const oauth = new OAuth.OAuth( 'https://www.hatena.com/oauth/initiate', 'https://www.hatena.com/oauth/token', api.COMSUMER_KEY, api.COMSUMER_SECRET, '1.0', 'oob', 'HMAC-SHA1' ); //リクエストトークン取得 await oauth.getOAuthRequestToken({ "scope": "read_public,write_public,read_private,write_private" }, async (err, request_token, request_token_secret, results) => { console.log('==============='); console.log(err); console.log("request token: " + request_token); console.log('==============='); console.log('redirectUrl: https://www.hatena.ne.jp/oauth/authorize?oauth_token=' + request_token); //ヘッダーを定義 const headers = {'Content-Type':'application/json'}; //オプションを定義 const options = { url: 'https://www.hatena.ne.jp/login', method: 'POST', headers: headers, json: true, form: { 'name': 'AccountID', 'password': 'hogehoge' } }; //cookie取得してログイン状態にする await request(options, async (error, response, body) => { const cookie = response.headers['set-cookie']; if (error === null && cookie !== undefined){ const _rk = (cookie as string[])[5].match("(rk=.*); domain"); if (_rk !== null){ const rk = _rk[1]; console.log('rk: ' + rk); //リクエストトークン付与してリダイレクト await axios.get('https://www.hatena.ne.jp/oauth/authorize', { params: { oauth_token: request_token }, headers: { cookie: rk } }).then(async (results) => { console.log(results); const data = results.data as string; const _rkm = data.match("name=\"rkm\" value=\"(.*)\""); if (_rkm !== null){ const rkm = _rkm[1]; console.log('rkm: ' + rkm); //oauth_verifierの取得 await axios.post('https://www.hatena.ne.jp/oauth/authorize', null, { params: { rkm: rkm, oauth_token: request_token, }, headers: { cookie: rk } }).then(async (results) => { const data = results.data as string; const _verifier = data.match("<div class=verifier><pre>(.*)</pre></div>"); if (_verifier !== null){ const verifier = _verifier[1]; console.log('verifier: ' + verifier); //Access Token の取得 await oauth.getOAuthAccessToken(request_token, request_token_secret, verifier, (err, accessToken, accessTokenSecret, parsedQueryString) => { if (err === null){ console.log(">>>Congraturations!!<<<"); console.log('AccessToken: ' + accessToken); console.log('AccessTokenSecret: ' + accessTokenSecret); console.log('ParsedQueryString: ' + parsedQueryString); } else { console.log(err); } }); } }).catch((err) => { console.log(err); }); } }).catch((err) => { console.log(err); }); } } else { console.log(error); } }); }); }
リクエストトークンの取得
await oauth.getOAuthRequestToken({ "scope": "read_public,write_public,read_private,write_private" }, async (err, request_token, request_token_secret, results) => { ... console.log('redirectUrl: https://www.hatena.ne.jp/oauth/authorize?oauth_token=' + request_token);
oauth#getOAuthRequestToken
に適切なscope
を渡し、request_token
を取得します。
次に取得したリクエストトークンを連携許可 URL (https://www.hatena.ne.jp/oauth/authorize) に付与してリダイレクトしますが、この際はてなにログインしていないと下記のようなフォームが表示され操作が複雑になってしまいます。
仮にログイン状態であれば、下記のように許可・拒否ボタンが表示されるのみで処理が簡単になります。
このログイン状態の判定は Cookie を見ることで行っているらしく、通常プログラム上から連携許可 URL にリダイレクトすると Cookie が無いので未ログイン状態として扱われてしまいます(詳しいことはわかりません。すみません。)。
そのため、はてなにプログラム上からログインし、headers
から Cookie(正確にはその中のrk
という値)を取得、そして「許可ボタンを押した」相当の操作を実行します。
はてなログイン処理
//ヘッダーを定義 const headers = {'Content-Type':'application/json'}; //オプションを定義 const options = { url: 'https://www.hatena.ne.jp/login', method: 'POST', headers: headers, json: true, form: { 'name': 'AccountID', 'password': 'hogehoge' } }; //cookie取得してログイン状態にする await request(options, async (error, response, body) => { const cookie = response.headers['set-cookie']; if (error === null && cookie !== undefined){ const _rk = (cookie as string[])[5].match("(rk=.*); domain"); if (_rk !== null){ const rk = _rk[1]; console.log('rk: ' + rk);
次に、取得したrk
を連携許可 URL に付与してリクエストします。
連携許可処理(oauth_verifier の取得)
//リクエストトークン付与してリダイレクト await axios.get('https://www.hatena.ne.jp/oauth/authorize'), { params: { oauth_token: request_token }, headers: { cookie: rk } }).then(async (results) => { console.log(results); const data = results.data as string; const _rkm = data.match("name=\"rkm\" value=\"(.*)\""); if (_rkm !== null){ const rkm = _rkm[1]; console.log('rkm: ' + rkm); //oauth_verifierの取得 await axios.post('https://www.hatena.ne.jp/oauth/authorize', null, { params: { rkm: rkm, oauth_token: request_token, }, headers: { cookie: rk } }).then(async (results) => { const data = results.data as string; const _verifier = data.match("<div class=verifier><pre>(.*)</pre></div>"); if (_verifier !== null){ const verifier = _verifier[1]; console.log('verifier: ' + verifier);
まず最初は、axios.get
でrequest_token
とrk
を付与してリクエストします。
すると上に挙げたような許可・拒否ページが表示され、許可するためにrkm
という値を取得します。
rkm
を取得したら、今度は同じ URL に対してrkm
を付与し POST します。
認証がうまくいけば、verifier
という値が返ってくるはずです。これを使って、最後に Access Token を取得します。
Access Token の取得
//Access Token の取得 await oauth.getOAuthAccessToken(request_token, request_token_secret, verifier, (err, accessToken, accessTokenSecret, parsedQueryString) => { if (err === null){ console.log(">>>Congraturations!!<<<"); console.log('AccessToken: ' + accessToken); console.log('AccessTokenSecret: ' + accessTokenSecret); console.log('ParsedQueryString: ' + parsedQueryString); } else { console.log(err); } });
request_token, request_token_secret, verifier
を引数にしてoauth#getOAuthAccessToken
を実行すると、めでたく Access Token が取得できます(問題なければ←)。
所感
TypeScript の文法を理解するのはそんなに苦ではなかったですが、Http リクエストがつらかった……全然わからん。
取得した Access Token / Access Token Secret を保存しておくことで、再認証無しにはてな API を叩くことができます(たぶん。未実装←)
これでやっと拡張機能が開発できるー