Google Apps Scriptで複数のOAuth認証を管理する【GAS】
業務でGoogle Apps Scriptを使う際に、各種ウェブサービスの操作を行うにあたってはもはや当たり前になってるOAuth2.0認証をしてリクエストを投げる手法。ライブラリを使って実現しますが、基本的には1つのサービスに対してしか対応していません。複数のウェブサービスを横断してやり取りするには、工夫が必要です。
今回はそれを実現する手法をまとめました。また、複数サービスのユーザ一覧を1枚にまとめる方法についても追記しています。
目次
今回利用するスプレッドシート
- 複数OAuth認証を行う - Google Spreadsheet
これまでも取り上げてきたOAuth2.0認証を行うものなのですが、同時に複数のサービスに対して認証をして、個別にそれらに関する認証情報をキープして、使う際に柔軟に取り出せるようにする必要があります。
事前準備
GAS側の準備
以下の手順でOAuth2 library for Google Apps Scriptライブラリを追加しましょう。
- スクリプトエディタを開きます。
- サイドバーよりより「ライブラリ」の+ボタンをクリック
- ライブラリを追加欄に「1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF」を追加します。
- 今回はバージョンは43を選択してみます。
- 保存ボタンを押して完了
これで、OAuth2.0認証にまつわる様々な関数を手軽に利用できるようになります。
図:ライブラリを追加した様子
複数サービスの認証情報ファイル
スクリプトプロパティに保存しても良いのですが、今回は複数のウェブサービスのClient IDやSecret、リクエストURLなどを一纏めにしたoauth.jsonというファイルを作成し、ドライブに配置してその情報を取得して使うようにしてあります。
oauth.jsonの仕様は以下のような感じ。利用するウェブサービスによっては項目が増えると思うので、そのへんを上手くまとめておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "webex":{ "clientid":"ここにClient IDをいれる", "secret":"ここにシークレットを入れる", "authurl":"ここにAuthorization URLを入れる", "token":"ここにTokenリクエストURLを入れる", "scope":"ここにスコープを入れる", "orgId":"ここに組織IDをいれる" }, "microsoft365":{ "clientid":"ここにClient IDをいれる", "secret":"ここにシークレットを入れる", "authurl":"ここにAuthorization URLを入れる", "token":"ここにTokenリクエストURLを入れる", "scope":"ここにスコープをいれる" } } |
今回は2つのウェブサービス。殆ど似ているのですが、webexだけorgIdが必要なので追加しています。また、ウェブサービスによっては当然scopeというものがなかったりするので、サービス毎にきちんと値を入れたり外したりが必要です。このファイルを読み込んで利用します。
ソースコード
認証を行うフロー
認証に用いるoauth.jsonファイルのIDを冒頭に記述しておき、それぞれのサービス認証用にstartoauthを呼び出す関数を用意しておく。startoauth関数は引数でサービス名を受け取って処理をするように作っておく。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//clientidのまとめファイル var oauthjson = "oauth.jsonファイルのIDをここに入力"; //webex認証 function webexauth(){ //Webexとして認証を開始 startoauth("webex"); } //Microsoft365認証 function m365auth(){ //microsoft365として認証を開始 startoauth("microsoft365"); } |
実際に認証をする側のコードもこれまでのコードよりも工夫が必要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
//OAuth認証を実行kする function startoauth(propman){ //UIを取得する var ui = SpreadsheetApp.getUi(); //tempauthを書き換える var prop = PropertiesService.getScriptProperties(); prop.setProperty("tempauth",propman); //認証済みかチェックする var service = checkOAuth(); if (!service.hasAccess()) { //認証画面を出力 var output = HtmlService.createHtmlOutputFromFile('template').setHeight(450).setWidth(500).setSandboxMode(HtmlService.SandboxMode.IFRAME); ui.showModalDialog(output, 'OAuth2.0認証'); } else { //認証済みなので終了する ui.alert("すでに認証済みです。"); } } //アクセストークンURLを含んだHTMLを返す関数 function authpage(){ var service = checkOAuth(); var authorizationUrl = service.getAuthorizationUrl(); var html = "<center><b><a href='" + authorizationUrl + "' target='_blank' onclick='closeMe();'>アクセス承認</a></b></center>" return html; } //認証チェック function checkOAuth() { //プロパティからサービス名を取得する var prop = PropertiesService.getScriptProperties(); var propname = prop.getProperty("tempauth") //propnameに応じたoauth認証データを取り出す(oauth.jsonのデータを取り出す) var file = DriveApp.getFileById(oauthjson); var oauthdata = JSON.parse(file.getBlob().getDataAsString("UTF-8")); //clientid, secret, authurl, tokenurlを取り出す let authurl = oauthdata[propname].authurl; let tokenurl = oauthdata[propname].token; let clientid = oauthdata[propname].clientid; let secret = oauthdata[propname].secret; //各サービス固有の情報を元に分岐 let scope = ""; switch(propname){ case "webex": //スコープを取得 scope = oauthdata[propname].scope; return OAuth2.createService(propname) .setAuthorizationBaseUrl(authurl) .setTokenUrl(tokenurl) .setClientId(clientid) .setClientSecret(secret) .setCallbackFunction("authCallback") //認証を受けたら受け取る関数を指定する .setPropertyStore(PropertiesService.getScriptProperties()) //スクリプトプロパティに保存する .setScope(scope) case "microsoft365": //スコープを取得 scope = oauthdata[propname].scope; return OAuth2.createService(propname) .setAuthorizationBaseUrl(authurl) .setTokenUrl(tokenurl) .setClientId(clientid) .setClientSecret(secret) .setCallbackFunction("authCallback") //認証を受けたら受け取る関数を指定する .setPropertyStore(PropertiesService.getScriptProperties()) //スクリプトプロパティに保存する .setScope(scope); break; } } //認証コールバック function authCallback(request) { var service = checkOAuth(); var isAuthorized = service.handleCallback(request); if (isAuthorized) { return HtmlService.createHtmlOutput("認証に成功しました。ページを閉じてください。"); } else { return HtmlService.createHtmlOutput("認証に失敗しました。"); } } //ログアウト function reset(propname) { //プロパティにサービス名をセットする var prop = PropertiesService.getScriptProperties(); prop.setProperty("tempauth",propname); checkOAuth().reset(); SpreadsheetApp.getUi().alert("ログアウトしました。") } |
- startoauth、resetではtempauthのプロパティの値をサービス名で書き換えておく。
- authpage、authCallbackは特に変更なし
- checkOAuthでは呼び出すサービス名をtempauthからロードしておく
- checkOAuthではDriveAppでファイルを読み込ませた後に、共通項目のClient ID, Secret, authurl, tokenurlを取得しておく
- サービス名で分岐させてサービスの数だけOAuth2.createServiceを用意しておく。
- setPropertyStoreでPropertiesService.getScriptProperties()を指定していますが、 PropertiesService.getUserProperties(); とすればユーザ毎に認証情報が保存され読み出されるマルチユーザ仕様になります。
- マルチユーザで使う場合は、複数名同時にアクセスしてくる可能性も考えて、tempauthもgetUserProperties管理にすると良いでしょう。また、スプレッドシートへの書き出しは、排他制御を用いるとデータが壊れずに済みます。
こうすることで、1つの処理系で複数のOAuth2.0認証のやり取りが出来るようになります。サービス毎にリクエストする度にcheckOAuthが呼び出されるわけで、リクエスト前にtempauthの値を各呼び出し元で書き換える手間はありますが、キレイに認証データが格納されます。
サービス毎にOAuth2.createServiceの内容が異なるので、このような工夫が必要となります。
特定のサービスだけシートから除去する
今回はユーザ一覧を取得して、シートのデータを洗替で書き換えるので、特定のサービス名を持つものを除外するコードを用意してあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//特定のサービス名のレコードを一括消去する function clearServiceRec(servicename){ //シートを取得する let prop = PropertiesService.getScriptProperties(); let ss = SpreadsheetApp.openById("ここにこのファイルのIDをいれる") //シートデータを一括で取得する var license = ss.getSheetByName("service") var clearman = license.getRange("A2:E") var sheet = clearman.getValues(); //servicenameに一致するレコードを配列から除外する const new_array = sheet.filter(function(array){ return array[3] != servicename }); //シートデータをクリアする clearman.clearContent(); //シートデータを貼り付ける let lastColumn = new_array[0].length; //カラムの数を取得する let lastRow = new_array.length; //行の数を取得する license.getRange(2,1,lastRow,lastColumn).setValues(new_array); //return return 0; } |
- サービス名を指定してそのサービス名が含まれるデータを除外するコードです
- 一括でデータをsheetに読み込んでおき、配列からfilterを使って除外する
- シートのデータをクリアした後に除外した新しい配列で上書きする
サービスを取得してシートに書き出す
以下はMicrosoft365のユーザ一覧を取得して書き出すコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
//Microsoftアカウントのユーザリストを取得する function getM365Users() { //uiを取得する let ui = SpreadsheetApp.getUi(); //サービス名を指定 let servicename = "microsoft365"; //シートを取得する let prop = PropertiesService.getScriptProperties(); let ss = SpreadsheetApp.openById("ここにこのファイルのIDを入れる") let sheet = ss.getSheetByName("service"); //propnameを書き換える prop.setProperty("tempauth",servicename); //トークン確認 var service = checkOAuth(); if(service.hasAccess()) { //エンドポイントを構築 var endpoint = "https://graph.microsoft.com/beta/users"; //リクエストヘッダ let header = { "Authorization": 'Bearer ' + service.getAccessToken(), "content-type": "application/json" } //リクエストオプション let options = { method: "GET", headers: header, muteHttpExceptions: true } //リクエスト実行 const response = UrlFetchApp.fetch(endpoint, options); //リクエスト結果を取得する const result = JSON.parse(response.getContentText()); //リクエスト結果を配列に格納する var array = []; //レスポンスデータを取り出す const retman = result.value; for(var i = 0;i<retman.length;i++){ //レコードを一個取り出す。 let rec = retman[i]; //書き込み用一時配列を用意 let temparr = [ rec.id, rec.displayName, rec.mail, servicename, "", ]; //arrayに追加する array.push(temparr) } //対象のサービスのデータをまず削除する let ret = clearServiceRec(servicename); //スプレッドシートの最終行を取得する let endrow = Number(sheet.getLastRow()) + 1; //スプレッドシートに書き出しする let lastColumn = array[0].length; //カラムの数を取得する let lastRow = array.length; //行の数を取得する sheet.getRange(endrow,1,lastRow,lastColumn).setValues(array); //終了 ui.alert(servicename + "のユーザ一覧を出力しました。") }else{ ui.alert("認証が実行されていませんよ。"); } } |
- OAuth認証データを呼び出す前に、tempauthに利用するサービス名を書き込んでおく必要があります。
- checkOAuthでtempauthの値を元に値を取り出してリクエストする
- clearServiceRecでデータを特定サービスのデータだけ消去したのちに一括で上書きし直す