electronでAzure AD認証を行い、Graph APIを叩く – 実装編
前回の記事、electronにてMicrosoft Azure ADのAccess Tokenの取得まで行いました。しかし、前回の項目では「安全なトークンの保存」であったり、「Refresh Tokenを使ってAccess Tokenを更新」といった項目が実装されていません。
今回の項目では、これらの項目を改造項目として加えて、認証周りを完成させてみたいと思います。Access Tokenが取得できていれば、あとはGraph APIだけでなく、Azureの各種サービスのAPIの実行も可能になります。
今回追加するモジュール
Windowsに於けるelectron向けにKeytarのインストールは非常に手間が掛かります。一度環境を構築してしまえば問題ないのですが、electronやkeytarのバージョン全てが利用できるわけではない点に注意。
社内プロキシーを超える
社内向けのアプリを作る上での実は一番の障害は、「プロキシーサーバ」だと思います。npmにしかり、electron、expressそれぞれにプロキシーを超える設定をする必要がある場合があります(特に大企業の場合、普通にプロキシーサーバ経由になってる場合が多く、これを超える設定を行わないと、外部と通信が出来ない)
electron側の設定
こちらは簡単です。プロキシーサーバもしくはプロキシーのpacファイルの指定を1行追加すれば、外部との通信が可能になります。これを入れておかないと、外部のCSSなどもロードされないので、注意。
//プロキシーサーバ直の場合 app.commandLine.appendSwitch('proxy-server', 'http://192.168.1.2:8080'); //プロキシーのPACファイルの場合 app.commandLine.appendSwitch('proxy-pac-url', 'http://192.168.1.2:8080/proxy.pac');
図:プロキシー経由させずにDNSエラーが出る
パッケージを追加
今回、expressをプロキシーの背後で動かす為に、httpx-proxy-agent-configというモジュールが必要でした。公式サイトなどでのコマンドラインで打つとインストールが出来ません。以下のコマンドでインストールが可能です。
npm i httpx-proxy-agent-config
関連モジュールもインストールされて、これでexpressからプロキシー経由で外に出る事が可能です。また、requestモジュール等にも別途Proxy設定せずとも有効になるので、非常に便利なモジュールです。
また、npmで追加できない場合、githubからファイルを拾ってきて手動で導入も可能です。一応記しておきます。
- Githubのページにアクセスして、右上にあるClone or DownloadでZIPでダウンロード
- ファイルを解凍する
- 解凍したフォルダの名前はhttpx-proxy-agent-configにリネーム
- 中に入るとpackageというフォルダがあるので、さらに入る
- この中にあるファイル類をhttpx-proxy-agent-configフォルダ直下に全部移動させる
- httpx-proxy-agent-configフォルダをプロジェクトのnode_modulesフォルダへ移動する
- ターミナルから入って、プロジェクトの中のnode_modulesへ移動。httpx-proxy-agent-configのフォルダに入る
- npm installを実行
- プロジェクト直下のpackage.jsonのdependenciesに以下の行を追加する
"httpx-proxy-agent-config": "0.0.4",
express側の設定
expressのapp.js側に追加するコードは以下の通り。
var proxy = require('httpx-proxy-agent-config'); //プロキシーの設定 proxy.install({ http_proxy: 'http://proxyのURL:8080', https_proxy: 'http://proxyのURL:8080', //blacklist: ['localhost'] });
たったこれだけです。http_proxyとhttps_proxyは環境によっては同じURLで問題ないかと。ただし、blacklistについてコメントアウトしてありますが、blacklistに入れたものはこのモジュールがブロックするので、注意してください。今回はelectronがlocalhost:3000にアクセスする必要があるので、localhostなどもblacklistには入れません。
以降のexpressの外部への通信はすべてプロキシー経由になります。
Keytarでパスワードの安全保管
Access TokenやRefresh TokenはAPIを叩く場合に渡す非常に重要なコードです。これをプログラムと同じディレクトリのファイルに保存したり、誰でもわかるような場所に素のまま格納するのは、危険です。そこで今回JSONに書き出したファイルを暗号化、その際のパスワードをOSの資格情報マネージャに格納し、保存・取り出しが出来るのが、keytarです(元々electron用ではなく、Node.js用)。
keytarのインストール
Windowsに於いて、keytarはnpmで簡単にインストールできません。リビルドする為にelectron-rebuildが必要です。また、インストール時にnode-gypによるネイティブモジュール問題の解決や、python2.7系でなければ駄目など複雑です。この辺りの環境構築については、こちらのエントリーでまとめています。electronでnode.jsのモジュールでリビルドが必要なケースは必要な環境なので、構築しておきましょう。
ここでは、環境が整っていることを前提に、インストールをする手順だけを記述します。(Windowsでは、electronはv3.0.0, keytarは4.2.1を指定しています)。
- コマンドプロンプト(ターミナル)を起動する
- npm install keytar@4.2.1でkeytarをインストール
- node-gypによるネイティブモジュールコンパイルが始まる
- node-modulesフォルダ内にあるkeytarフォルダに移動する
- コマンドプロンプトでnode-gyp configureを実行する
- プロジェクトフォルダ直下に戻る
- electron-rebuild -w keytarにてkeytarをリビルドする
- Rebuild Complateが出れば完了。これで、keytarが使えるようになる
- electronのバージョン上げたり、Node.jsのバージョンを上げたりした場合にはリビルドが必要になりますので、ご注意ください(また、バージョン上げるとリビルドが失敗する可能性もあります)。
- ※keytarを使ったアプリをmacOSとWindowsの両方でリリースする場合は、それぞれに環境を作ってビルドするほうが良いと思います。
- ※64bit Windows上でrebuildした場合、64bit Windows上でしか動作しませんので注意。また、ia32でelectron-packagerでパッケージを作ろうとした場合にも同様のエラーが出ます。
図:Windowsの資格情報マネージャに登録できた様子
JSONファイルの書き出しと暗号化
前回までのコードですと、Access Tokenを直接textに書き出すという暴挙をしていました。Access TokenやRefresh Tokenがそのままなので、これを秘匿させたい。しかし、膨大な量のデータを資格情報マネージャに格納は出来ないので、JSONファイルを生成して於いて、まるごと暗号化(そのパスワードは設定でkeytarを用いて資格情報マネージャに入れておく)という仕組みを考えてみました(よって設定用の画面が必要になります)。
暗号化パスワード設定用画面
設定用の画面として、setting.html、そこで使うjsやcss、images用のフォルダをプロジェクトフォルダ直下に作ります。また、他でも利用する機会があるので、jQueryをnpm i jqueryでインストールしておきましょう。
レンダラプロセス側コード(setting.html)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script> var $ = jQuery = require("jquery") </script> <script type="text/javascript" src="js/jquery-ui.min.js"></script> <link rel="stylesheet" href="css/jquery-ui.css"> <link rel="stylesheet" href="css/setting.css"> <script src="index.js"></script> <script> $(function() { $( "input[type=submit], a, button" ) .button() .click(function() { }); }); // IPC通信を行う var ipcRenderer = require( 'electron' ).ipcRenderer; window.onload = function () { //受信レンダラーの準備 testAsync(); }; //メインプロセス側からの非同期に通信を受信待機する(1回だけ実行) function testAsync() { //各種雑多なメッセージを受け取る ipcRenderer.on('msg', function(event,arg) { alert(arg); }); //保存済みデータを受け取る ipcRenderer.on('init',function(event,arg){ //受け取ったデータをHTMLのボックスに反映する var array = arg; //パスワードをテキストボックスに格納 document.getElementById("pass").value = array[0]; }) //エラーメッセージの表示 ipcRenderer.on('error',function(event,arg){ //エラーを受け取ったら何もしない }) //現在保存されてるパスワードを取得 ipcRenderer.send('async', "keytar"); } //キャンセル時に設定ウィンドウを閉じる function notsetting(){ ipcRenderer.send('closeset', "setting"); } //セッティング項目を保存する function savesetting(){ //入力値のvalidation var validata = ""; var array = []; //パスワード validata = document.getElementById("pass").value; if(validata == ""){ alert("パスワードが入っていませんよ"); document.getElementById("pass").focus(); return; }else{ array.push(validata); } //メインプロセスに処理を送る ipcRenderer.send('keytar', array); } </script> <title>Azure ADセッティング</title> </head> <body> <!-- セッティング項目を表示 --> <form class="contact_form" action="#" method="post" name="contact_form"> <ul> <li> <h2>プログラムの設定</h2> <span class="required_notification">*印は、必須入力項目です</span> </li> <li> <label for="name">暗号化用パスワード:</label> <input type="password" name="password" placeholder="********" style="width:100px" id="pass" required /> <span class="form_hint">Token暗号化用のパスワードを設定"</span> </li> </ul> </form> <p> <center> <button onClick='savesetting()' id="saveman" class="action" title='設定を保存する'>設定保存</button> <button onClick='notsetting()' id="cancelman" class="action" title='キャンセル'>キャンセル</button> </center> </p> </body> </html>
- setting.cssは主に画面のデザイン関係のこちらのサイトのCSSを利用しています。
メインプロセス側コード(index.js)
メインプロセス側には、以下の項目を追加しています。
- setwindowというBrowserWindowの制御をするコード
- IPC通信による資格情報マネージャの読み書きを制御するコード
- タスクトレイアイコン関係のコード
以下のコードは追加した部分だけです。
//追加モジュールの宣言 const keytar = require('keytar'); //タスクトレイ用 let tray = null; // Electronの初期化完了後に実行 app.on('ready', () => { ・・・中略・・・ //トレイのコンテキストメニューを設定 const contextMenu = Menu.buildFromTemplate([ {label:'設定', click(menuItem){ setwindowopen(); //setting.htmlを開く }}, {type:'separator'}, {label:'閉じる',click(menuItem){ //キャッシュを捨てる electron.session.defaultSession.clearCache(() => {}) //設定ウィンドウが開いていたら閉じる if (setWindow && !setWindow.isDestroyed()) { setWindow.close(); setWindow = null; } //閉じる process.exit(0); app.quit(); }} ]); //トレイアイコンを設定 tray = new Tray(__dirname + '/images/azure.ico') //ツールチップの設定 tray.setToolTip("Azure認証をするためだけのアプリ"); //右クリック時にコンテキストメニュー表示をセットする tray.on('right-click',() =>{ //メニューを表示 tray.popUpContextMenu(contextMenu); }); }) //setWindowを再度開くコマンド function setwindowopen(){ //ウィンドウの多重起動防止 if (setWindow && !setWindow.isDestroyed()) { setWindow.show(); setWindow.focus(); return; } // メイン画面の表示。ウィンドウの幅、高さを指定できる setWindow = new BrowserWindow({ 'width': 520, 'height': 250, 'autoHideMenuBar':true, //nodeIntegrationを有効にしないとrenderProcessでrequireを使えない。v5.0.0ではデフォルトで廃止 webPreferences: { nodeIntegration: true }, 'resizable':false, 'fullscreenable':false, 'fullscreen':false, 'alwaysOnTop':true, 'modal':true, //'closable':false }); //初期ページの表示 setWindow.loadURL('file://' + __dirname + '/setting.html'); setWindow.on('closed', function() { electron.session.defaultSession.clearCache(() => {}) setWindow = null; }); // 全てのウィンドウが閉じたときの処理 app.on('window-all-closed', () => { //なにもしない //これをいれておかないと、全部のウィンドウが閉じられるとアプリまで閉じてしまう、。 }); } //設定関係担当 ipcMain.on('async', function( event, args){ //コマンド名によって処理を開始 switch(args){ //資格情報マネージャのパスワードを取得 case "keytar": var array = []; //key名を設定する var servicename = "azuread_auth"; var tempid = "temp"; //key名で探索して返す if(keytar.findPassword(servicename)){ const secret = keytar.getPassword(servicename,tempid); secret.then((result) => { array.push(result); event.sender.send('init', array); return; }); }else{ array.push(""); event.sender.send('init', array); } break; } }); //ウィンドウをコントロール担当 ipcMain.on('closeset', function( event, args ){ //コマンド名によって処理を開始 switch(args){ case "setting": //セッティングウィンドウを非表示にする setWindow.close(); break; default: break; } }); //keytar関係をコントロールする ipcMain.on('keytar', function( event, args ){ //配列データを受け取る var array = args; //サービス名を構築する var servicename = "azuread_auth"; //キーワードを保存する keytar.setPassword(servicename,"temp",array[0]); //セッティングウィンドウを非表示にする setWindow.hide(); });
- タスクトレイにアイコンを表示し、右クリックメニューで設定を出せるようにしています。
- setting.htmlを開いた時に、指定のkeyのパスワードを取得して返すようにしています。ない場合には空のまま
- パスワード保存時には、keytarによって資格情報マネージャに格納します。
- 取得時、格納時共にIDが本来必要ですが、今回の場合IDは不要なので「temp」としています。
- keyであるservicenameはなるべく被らないものを利用しましょう。
図:このパスワードでaccess tokenを暗号化します
JSONファイル生成と暗号を施す
Node.jsにて暗号化・復号化にCryptoモジュールを利用します(標準装備なので別途インストールは不要)。以前別のエントリーでも復号化だけは実際に作っています。今回は資格情報マネージャに格納されているパスワードをkeyに利用して、AES192bitで生成したJSONデータを暗号化してみたいと思います。
また、生成時に復号化してTokenが切れているかチェックしやすいように、取得時の日付時間および期限の日付時間もJSONに含めて置こうと思います。本来はハッシュ化したワードで暗号・復号化をすべき所ですが、今回は単純にAESで暗号・復号化します。
app.jsのコード
//暗号・複合モジュール var crypto = require("crypto"); //暗号化の為の関数 function encryptAes(json){ //サービス名を構築する var servicename = "azuread_auth"; var tempid = "temp"; //key名で探索して返す if(keytar.findPassword(servicename)){ const secret = keytar.getPassword(servicename,tempid); secret.then((result) => { //暗号化 var cipher = crypto.createCipher("aes192", result); var cryptman = Buffer.concat([cipher.update(json), cipher.final()]); //ファイル書き出し fs.writeFileSync("user.json", cryptman); return true; }); }else{ return false; } } //認証フロー passport.use(new strategy(options, (iss, sub, profile, access_token, refresh_token, done) => { try{ if (profile.oid) { //トークン取得日時 var tokenday = new Date(); //アクセストークンリミットの時間を生成(1時間) var aclimit = tokenday.setHours(tokenday.getHours() + 1); //リフレッシュトークンリミットの日付を生成(90日) var rtlimit = tokenday.setDate(tokenday.getDate() + 90); //データを組み立て var userdata = {}; userdata.access_token = access_token; userdata.refresh_token = refresh_token; userdata.atlimit = aclimit; userdata.rtlimit = rtlimit; //Access Tokenをテキストに書き出す //暗号化 encryptAes(JSON.stringify(userdata)); //データを組み立て(index.html側用) const user = { iss, sub, profile, access_token, refresh_token }; //ユーザデータを返す return done(null, user); } //取得データを返す return done(null, false); }catch(err){ //エラートラップ return done(null, err); } } ));
- 暗号化の為のencryptAes関数を用意。取得したTokenデータ(JSON形式)を暗号化して、user.jsonというファイルで保存します。
- アクセストークンの期限は1時間後なので、そのデータ(aclimit)を作ってuserdataに含めておく(リフレッシュ時に使用するため)
- リフレッシュトークンの期限は90日後なので、そのデータ(rtlimit)を作ってuserdataに含めておく(再度認証が必要かどうか確認時に使用するため)
- 今回復号化のルーチンは、index.js側に用意してるので、こちらには記載していません。
- 暗号化は、資格情報マネージャに登録してあるパスワードを使って暗号化しています(そのためにkeytarを利用)。
- 暗号化する場合には、encryptAes関数には、JSON.stringifyでJSON文字列に変換してから渡す必要があります。
図:暗号化されたTokenデータファイル
JSONファイルの復号化とAccess Tokenの取得
Azureの各種APIを叩く為には、Access Tokenが必要です。しかし、前項でTokenデータは暗号化してありますので、利用時には復号化してあげなければTokenデータを取り出せません。
decryptAes関数にてuser.jsonのデータを復号化してコンソールに表示するというものを作ってみました。実運用時はapp.js側に書くことになるかと思います。
app.js側のコード
//暗号・複合モジュール var crypto = require("crypto"); //復号化の為の関数 function decryptAes(callback){ //user.jsonファイルを取り込む var json = fs.readFileSync("user.json"); //サービス名を構築する var servicename = "azuread_auth"; var tempid = "temp"; //key名で探索して返す if(keytar.findPassword(servicename)){ const secret = keytar.getPassword(servicename,tempid); secret.then((result) => { //復号化 var decipher = crypto.createDecipher('aes192', result); var cryptman = Buffer.concat([decipher.update(json), decipher.final()]); //復号化したデータを返す(Stringで変換する) callback(String(cryptman)); }); }else{ return false; } }
- index.js側にも、cryptoモジュールを読み込ませておきます。
- 復号化の為に、decryptAes関数を用意。user.jsonを暗号化されたまま、まずは取り込みます。
- 復号化したデータをそのまま返しても、返された側で取り出せないので、Stringで文字列に変換します。
- 復号化は、資格情報マネージャに登録してあるパスワードを使って復号化しています(そのためにkeytarを利用)。
- decryptAes関数をcallbackにしておかないと、呼び出し元で呼び出されても返り値を受け取れません。
- 呼び出し元では以下のようなコードで、呼び出してcallbackで受け取ってコンソール表示しています。
//decryptAes関数を呼び出してAccess Tokenを取り出す decryptAes(function(ret){ //JSONをParseする var json = JSON.parse(ret); //access_tokenを取り出す console.log(json.access_token); })
図:無事に復号化して、Access Tokenだけ取り出せた
トークンリフレッシュ
取得したAccess Tokenはおよそ1時間で失効します。其のため、継続的に使うには毎回ログインし直さないといけない。これではあまりにも不便です。そこで用意されているのがRefresh Token。これを使って新しいAccess Tokenを自動的に取得して、継続してアプリを使えるようにする仕組みが、この手のアプリケーションでは必須です。
requestモジュールをインストールする
今回使用するのは、requestというモジュール。passport自体にリフレッシュ機能が備わっていないので、これを使う必要があります。インストール自体はとても簡単。
npm i request
これだけ。simple-oauth2やpassport-oauth2-refreshなどのモジュールもあるのですが、どちらも自分の環境では使えなかったので、自力でrequestモジュールで更新するコードを構築しました。
リフレッシュするコード
特定のモジュールがなくとも、requestモジュールで組み立ててあげれば、Refresh Tokenにてトークンの取得が可能です。きちんと、Portal側でスコープや権限を割り当ててあり、テナントIDに間違いがなく、アクセスURLも正しいものであれば、エラーに遭遇せずに済むでしょう。HTML側は今後実際に使うウィンドウになるので、余計なコードを排除しました。
app.js側コード
const { dialog } = require('electron'); //HTTPリクエスト用モジュールの読み込み var request = require('request'); //認証用の設定 var tenant = "ここにテナントのIDを入れる"; var ref = "https://login.microsoftonline.com/"+ tenant +"/oauth2/token" //初期ページ app.get('/', (req, res) => { res.render('index.html'); }); //サインアウトをクリックした時 app.get('/auth/signout', (req, res) => { req.session.destroy((err) => { if (err) { res.send(err) return } //user.jsonファイルを廃棄する try { fs.unlinkSync('user.json'); //ダイアログオプション var options ={ type:'info', title:"サインアウト", button:['OK'], message:'トークンは廃棄されました', detail: "Access Tokenは廃棄されました。" } dialog.showMessageBox(null,options); } catch (error) { //何もしない } //トップページに移動する res.redirect('/') }) } ); //Access Tokenをリフレッシュ app.get('/auth/refresh', (req, res) => { //user.jsonを復号化して各種値を取り出す decryptAes(function(ret){ //JSONデータを受け取る var json = JSON.parse(ret); var rtlimit = json.rtlimit; var access_token = json.access_token; var refresh_token = json.refresh_token; //連想配列を作ってあげる var token ={ rtlimit : rtlimit, access_token : access_token, refresh_token : refresh_token } //tokenリフレッシュの実行 renewToken(json,function(ret){ //更新ステータスを取得する var status = ret[0]; //ステータスによって処理を分岐 if(status == true){ //既存のJSONに値をマージする const user = { "access_token": ret[1].access_token, "refresh_token": ret[1].refresh_token, "atlimit": ret[1].atlimit, "rtlimit": ret[1].rtlimit }; //Access Tokenをテキストに書き出す //暗号化 encryptAes(JSON.stringify(user)); //ダイアログオプション var options ={ type:'info', title:"認証成功", button:['OK'], message:'トークンリフレッシュ成功', detail: "Access Tokenのリフレッシュが成功しました。" } dialog.showMessageBox(null,options); }else{ //ダイアログオプション var options ={ type:'info', title:"エラー", button:['OK'], message:'認証エラーが発生しました', detail:ret[1] } dialog.showMessageBox(null,options); } }); }) }); //暗号化の為の関数 function encryptAes(json){ //サービス名を構築する var servicename = "azuread_auth"; var tempid = "temp"; //key名で探索して返す if(keytar.findPassword(servicename)){ const secret = keytar.getPassword(servicename,tempid); secret.then((result) => { //暗号化 var cipher = crypto.createCipher("aes192", result); var cryptman = Buffer.concat([cipher.update(json), cipher.final()]); fs.writeFileSync("user.json", cryptman); return true; }); }else{ return false; } } //復号化の為の関数 function decryptAes(callback){ var json = fs.readFileSync("user.json"); //サービス名を構築する var servicename = "azuread_auth"; var tempid = "temp"; //key名で探索して返す if(keytar.findPassword(servicename)){ const secret = keytar.getPassword(servicename,tempid); secret.then((result) => { //復号化 var decipher = crypto.createDecipher('aes192', result); var cryptman = Buffer.concat([decipher.update(json), decipher.final()]); //復号化したデータを返す(stringで変換する) callback(String(cryptman)); }); }else{ return false; } } //リフレッシュトークンを使って新しいTokenを取得する function renewToken(token,callback){ //リフレッシュトークンの日付時刻 var dt = new Date(token.rtlimit); //現在の日付時刻 var dt2 = new Date(); //差分を調べる var difftime = dt - dt2; //取得したRefresh Tokenの期限日と現在日付を比較する if(dt <= dt2){ var headers = { "Content-type": "application/x-www-form-urlencoded", } var config = { client_id: clientID, client_secret: clientsecret, scope: "profile%20Files.ReadWrite%20offline_access%20User.Read", redirect_uri: redirecturi, refresh_token: token.refresh_token, grant_type: "refresh_token" } //JSON形式だとNGなので const payload = Object.keys(config).map(key => key + "=" + encodeURIComponent(config[key])).join("&") //POST通信オプションの設定 var options = { url: ref, headers: headers, method: 'POST', body: payload, } //requestにて新しいトークンを取得する request(options, function (error, response, body) { //ステータスコードを取得する var status = response.statusCode; if (!error && status == 200) { //データのパース var json = JSON.parse(body); //トークン取得日時 var tokenday = new Date(); //アクセストークンリミットの時間を生成(1時間) var atlimit = tokenday.setHours(tokenday.getHours() + 1); //リフレッシュトークンリミットの日付を生成(90日) var rtlimit = tokenday.setDate(tokenday.getDate() + 90); //トークンデータを返す var newToken = {}; newToken.atlimit = atlimit; newToken.rtlimit = rtlimit; newToken.access_token = json.access_token; newToken.refresh_token = json.refresh_token; callback([true,newToken]); }else{ //エラー処理 console.log("認証エラー"); callback([false,error]); return; } }); }else{ //リフレッシュトークン切れなので、再認証が必要 callback([false,"Refresh Tokenの期限切れ。再認証が必要です。"]); } }
- refresh tokenを取得する為のURL(変数ref)に注意してください。
- サインアウト時にuser.jsonファイルを廃棄するコードを追加してあります。また、現在特定のトークンをrevokeさせるAPIがAzureに存在しないので、このような処置をしています。
- refreshのリンクをクリックすると、user.jsonを復号化し、リフレッシュトークンの有効期限をチェックした後、renewTokenで新しいAccess Tokenを取得するようにしています。
- 新しいTokenデータは、encryptAes関数にて再び暗号化されて、user.jsonとして書き出されます。
- エラー等の通知は、dialogモジュールを利用しています。
- renewToken関数内で、requestモジュールを使ってPOST通信にて新しいTokenデータを取得しています。
- AzureのTokenエンドポイントはpayloadをJSON形式で受け付けていないため、access_token=xxxx&refresh_token=xxxxといった形式に変換してから格納しています。
- 渡すscopeですが、半角スペース区切り。但しこの時半角スペースはURLエンコードしておくことが要求されるので%20で区切ってあります。新規取得時と同じスコープを指定する必要があります。
- payloadに渡すgrant_typeはrefresh_tokenである必要があります。
- 無事に認証されて、status200が返ってくれば、新しいトークンが手に入ります。
- これで、APIを実行する場合、有効期限をチェックし、問題なければそのまま実行、切れていればRefresh Tokenで再取得して実行が可能になります。API叩き放題です。
HTML側コード
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <script src="app.js"></script> <title>Azure AD認証ダイアログ</title> </head> <body> <div class="container"> <h1>Microsoft Azure 認証</h1> <p> <ul> <li><a href="/auth/signin">signin</a></li> <li><a href="/auth/signout">signout</a></li> <li><a href="/auth/refresh">refresh</a></li> </ul> </div> </body> </html>
- こちら側は余計なコードを排除し、新たにrefreshを追加しました。
関連リンク
- passport-twitterでのTwitter認証をproxy環境下でも動作させる
- Microsoft Graph (Microsoft365) API のトークンを取得して更新する方法
- maliksahil/expressjs-passport-azure-ad
- How can I get Nodejs twitter-passport behind proxy?
- Cannot connect behind proxy #341
- bloublou2014/httpx-proxy-agent-config
- Passport-azure-ad passport plug-in refresh the token
- Behind proxy! #9
- Outlook のメール、予定表、および連絡先を取得する Node.js アプリの記述
- Microsoft Graph を使った Office 365 Node.js Connect サンプル
- Microsoft Graph を使って Node.js Express アプリを構築する
- ExpressとPassport.jsでOAuth2 (4)有効期限切れのアクセストークンをリフレッシュする
- proxy-agent - npm
- AWS SDK for Node.jsをProxy環境で使う
- Nodejsのtwitter-passportをプロキシの背後で取得するにはどうすればよいですか?
- Node.js 用のプロキシの設定 - AWS SDK for JavaScript
- chimurai/http-proxy-middleware
- Anyway to set proxy setting in passportjs?
- express.jsによるプロキシ
- Proxy with express.js
- Express + Passport と Angular でセッション管理するアプリを作ってみる
- Githubにあげた個人のnpmパッケージをインストールする方法
- OpenID Connect と Azure Active Directory を使用する Web アプリケーションへのアクセスの承認
- passport-azure-ad, validation of tokens
- Azure AD が発行するトークンの有効期間について
- fiznool/passport-oauth2-refresh
- JavaScriptのasync/awaitがPromiseよりもっと良い
- 意外と知られていないJavaScriptのnew Date()の使用方法
- Expose proxy options of request package #16
- Express Js using simple-oauth2 to access Microsoft Graph, possible proxy issue?
- Content-type is not JSON compatible while using Simple OAuth2 with NodeJS
- Refresh token [TypeError] #114
- 5.更新トークンを使用して新しいアクセス トークンを取得する
- Microsoft GraphでOAuthのアクセストークンを更新する仕組みを作る
- TypescriptからMicrosoft Graph API使ってSharePointやOneDrive上のExcelの情報を読み込む
- HTTP POST Body parameters to get OAuth2 token
- AAD: Revoke / Invalidate access tokens #12717