フォーム読み込み中
Google Workspaceを社内のオフィスサービスと利用している企業も多いでしょう。本記事では、Google Workspace内で使えるGoogle Apps Scriptを利用して、JWT発行サーバを構築する方法について解説します。
JWTを安全に運用するためには、サーバを構築して安全に運用をする必要がありますが、このサーバ運用の手間はできる限りかけたくありません。そこで、Google SheetのGoogle Apps Script(以後GAS)を活用する方法を検証したので、紹介をして行きます。
具体的な事例を交え、背景から説明していきます。
IBM Cloudで使える、IBM Watson Assistant(以後Assistant)というAI チャットボットを利用する際に弊社内で課題となりました。
Assistantでチャットボットを作成すると、HTMLのヘッダに埋め込んで利用できるJavaScriptのコードスニペットが発行されます。
<script> -snip- integrationID: "The ID of this integration" serviceInstanceID: "The ID of your service instance" -snip- </script>
Google WorkspaceにはGoogle SitesというWebサイト作成サービスがあります。HTMLやJavaScriptのコードを挿入ができます。このサービスにJavascriptを埋め込むと、Webのホスティングサーバの管理も、プログラミング知識も不要、あっという間に、問い合わせ任務を行うチャットボット付き、ポータルサイトが完成です。
ただし、次の課題が浮かび上がりました。
「JavaScriptのコードスニペットが外部に漏れた場合、情報漏洩につながるのではないか?」
統合するための各種IDは、作成したチャットボットごとにユニークです。インターネットからアクセスはできますが、推測できるようなIDでもないため、部外者が勝手に使うことはできません。統合用のID自体が、パスワードの役割を果たしていると見なしてもよいでしょう。
先の懸念は杞憂に過ぎない、とも思えますが、仮にこのIDが外部へ漏れた場合、漏れたことに気づく仕組みがなければ、永遠と情報が吸い上げられてしまいます。
AssistantはAPIが公開されており、会話をAPIでも制御できます。サーバを用意し、そこでログイン認証を設けたアプリケーションを構築することもできます。しかしこれでは、各部門が気軽に使うような用途からは外れてしまいます。
アプリケーションの設計知識を必要とせず、JavaScriptを埋め込んだだけで使える利用勝手の良さを維持しつつ、しかし懸念点を払拭できる方法はないでしょうか。
Assistantが発行するJavaScriptですが、認証トークンであるJWT(JSON Web Token)を有効にして利用することもできます。
JWTを使った場合のシーケンスです。真ん中がブラウザであり、右側が、IBMが管理するAssistantサーバです。JWTを使わない場合は、登場人物はこの2者だけでした。
参考:https://cloud.ibm.com/docs/assistant?topic=assistant-web-chat-security
JWTを使う場合は、左側の自前サーバを用意し連携させます。ブラウザ側のJavaScriptがAssistantと会話する前に、自前サーバ側でユーザ認証を通し、認証後に、自前サーバが秘密鍵で署名したJWT発行します。
公開鍵はAssistant側(IBMサーバ側)に事前に共有しておきます。IBM側サーバでJWTの署名検証を行い、Assistantと会話を始めるという仕組みです。
JWT発行後は、このトークンを使い回します。JWTには有効期限を設けることもできます。期限が切れた場合は、再度、上記のシーケンスを走らせます。
JavaScript漏洩への懸念への課題は解決です。ただし、サーバの構築、運用管理が必要になります。ノーコードで、IT専門の部門でなくても気軽に使える簡便さはなくなります。
JWTは使いたい、ただし、本格的なサーバ管理はしたくない。何かいい手段はないかと考えた結果、Google SheetsのGoogle Apps Script(以後GAS)の利用が候補としてあがりました。
GAS上では、HTML(JavaScript)のホスティングが可能です。さらに、JavaScriptからGAS内で定義したプログラムコードを呼び出せます。これがGASをJWT発行サーバにしようという意味です。
通信シーケンスは先の図と同じですが、JWT発行サーバをGoogle管理のGASが稼働する環境に委任することのメリットは大きいです。
企業内で、Goolge Workspaceを使う場合は、企業内のIT部門がルールを設け運用しているはずです。GASをサーバとして使うのですが、決して野良サーバとなることなく、企業コントロール配下の枠内に収まった使い方になります。また、GASでホスティングしたHTMLや、プログラムのロジックを使う場合は、認証と認可が走ります。誰であるか、何をさせるのかのロジックを開発者側が一切考慮する必要性がなくなります。
それでは、GASで準備を始めましょう。用意するファイルは2つです。
・index.html
・code.gas
index.htmlから見ていきます。JavaScript部分が少しややこしく思えるかもしれませんが、ほぼAssistantが発行するJavaScriptをそのまま流用して貼り付けているだけです。
変更している点は2点だけです。code.gs内の関数を呼び出すようにしています。この2つの関数だけご理解いただければ十分です。
◆ index.html
<!DOCTYPE html> <html> <head><base target="_top"></head> <body> <script> google.script.run.withSuccessHandler(function(credential) { window.watsonAssistantChatOptions = { integrationID: credential.integration_id, region: credential.region, serviceInstanceID: credential.instance_id, identityToken: null, showLauncher: false, openChatByDefault: true, hideCloseButton: true, onError: function(err) { console.error( "ERROR", err ) }, onLoad: function(instance) { instance.on({ type: 'identityTokenExpired', handler: function(event) { console.warn("TOKEN EXPIRED") return new Promise(function(resolve, reject) { google.script.run.withSuccessHandler(function(res) { event.identityToken = res resolve(); }) .getIdentityToken() }); }}); instance.updateLocale('ja'); instance.render(); }, }; setTimeout(function(){ const t = document.createElement('script'); t.src="https://web-chat.global.assistant.watson.appdomain.cloud/versions/" + (window.watsonAssistantChatOptions.clientVersion || 'latest') + "/WatsonAssistantChatEntry.js"; document.head.appendChild(t); }); }) .getCredential() </script> </body> </html>
◆ code.gs
続いて、code.gsです。JWT関係のライブラリが使えなかったので、手動でJWTを作成しています。
getCredentialはAssistantで利用する、Assistantが払い出すIDです。html内にベタ書きでもいいですが、背後に隠しておこうと先に説明した件のことです。
getIdentityToken内に、JWT署名用の秘密鍵を貼り付けます。公開鍵はAssistant側に持たせておきます。OpenSSL系のコマンドで発行する一般的な鍵です。
参考:https://cloud.ibm.com/docs/assistant?topic=assistant-web-chat-security
function doGet() { let template = HtmlService.createTemplateFromFile('index.html') return template.evaluate() } function getCredential() { return { integration_id: "***", region: "jp-tok", instance_id: "***" } } function getIdentityToken() { const SECRET_KEY = "***" let jwt = createJwt(SECRET_KEY) return jwt } function createJwt(p_key) { const jwtHeader = { "alg": "RS256", "typ": "JWT" }; const now = new Date(); now.setHours(now.getHours() + 1); const oneHourFromNowSeconds = now.getTime() / 1000; const jwtClaim = { "iss": "g.softbank.co.jp", "sub": Session.getActiveUser().getEmail(), "exp": oneHourFromNowSeconds, } const jwtHeaderBase64 = base64EncodeSafe(JSON.stringify(jwtHeader)); const jwtClaimBase64 = base64EncodeSafe(JSON.stringify(jwtClaim)); const signatureInput = jwtHeaderBase64 + "." + jwtClaimBase64; const signature = Utilities.computeRsaSha256Signature(signatureInput, p_key); const encodedSignature = base64EncodeSafe(signature); const jwt = signatureInput + "." + encodedSignature; return jwt; } function base64EncodeSafe(string) { var encoded = Utilities.base64EncodeWebSafe(string); return encoded.replace(/=/g, ""); }
GASでindex.htmlをデプロイしてみてください。またそのURLをGoogle Sitesに貼り付けてみてください。利用勝手は変わっていません。しかし内部はより安心セキュアなチャットボットです。
具体事例を元に解説したかったため、IBM CloudのWatson Assistantを使って説明しました。ただし、このような使い方は、IBMのチャットボットのみに限定されないことをご想像いただけるのではないでしょうか。
サーバを用意したいけれど、サーバのことはできる限り意識したくない、類似の要望と要件が色々と思いつきます。少しでもご参考になれば幸いです。
Google スプレッドシート、Gmail、Google カレンダー、Google Chat、Google ドライブ、Google Meet などのさまざまなサービスがあらゆる働き方に対応する業務効率化を実現します。
条件に該当するページがございません