自分用のダッシュボード(実測値を表示するだけの小さなWebアプリ)を、Google Apps Scriptで作って運用しています。今回、「特定の人だけ見られるようにする」という、ごく普通に思える要件で、実際にはかなりハマりました。同じことをやろうとしている人向けに、原因と対処を記録します。
やりたかったこと
- GASのWebアプリを、不特定多数ではなく、決まった数人のGoogleアカウントだけに見せたい
- 見る人のアカウントは、Google Workspace(組織用)のアカウントと、個人のGmailアカウントが混在している
最初にやったこと(うまくいかなかった)
GASのコードで、アクセスしてきた人のメールアドレスを取得して、許可リストと照合する方法を試しました。
function isAllowedViewer_() {
var email = Session.getActiveUser().getEmail();
return ALLOWED_LIST.indexOf(email) !== -1;
}
デプロイ設定は「Webアプリを実行するユーザー」を「アクセスしているユーザー」にし、アクセス権は「全員」にしました。これで動くはずでした。
実際に起きたこと
許可リストに入っているはずのアカウントでも、アクセスが弾かれました。
原因を調べると、Session.getActiveUser() は、スクリプトの所有者と、アクセスしている人が同じGoogle Workspaceドメインに属していないと、正しい値を返さないという仕様でした。個人のGmailアカウントや、別の組織のWorkspaceアカウントからアクセスすると、メールアドレスが空文字になります。
「アクセスしているユーザーとして実行する」に設定を変えても、この制限は変わりませんでした。OAuthスコープにuserinfo.emailを追加しても、今度は新規ユーザーの同意フローが正しく走らず、別のエラーになりました。
実際に機能した代替案
最終的に、GoogleアカウントでのIDの判定は諦めて、URLに秘密のキーを埋め込む方式に切り替えました。
function isAllowedViewer_(params) {
return params.key === SECRET_KEY;
}
「誰がアクセスしているか」ではなく「そのURLを知っているか」で判定する方式です。ブックマークするURLに?key=...を付けてもらう運用にすれば、実用上は十分機能します。
もう1つハマったこと: ページ内リンクがドメイン専用URLになる
Webアプリ内のリンク(「ページを移動する」ボタンなど)を作るとき、ScriptApp.getService().getUrl()でURLを取得して使っていました。ところが、Google Workspaceのアカウントで実行すると、このURLがそのドメイン専用の形式(/a/ドメイン名/macros/...)になることがあります。このURLは、別のドメインのアカウントからアクセスすると正しく機能しません。
対処法は、URLを正規化する関数を挟むことです。
function toGenericWebAppUrl_(url) {
return String(url || '')
.replace(/\/a\/macros\/[^\/]+\//, '/macros/')
.replace(/\/a\/[^\/]+\/macros\//, '/macros/');
}
分かったこと
- GASの「アクセスしている人を識別する」機能は、同じ組織(同じWorkspaceドメイン)内で完結する用途では機能するが、個人アカウントや複数組織が混ざる場面では前提が崩れる
- 「認証」と「認可」を、Googleアカウントの識別に頼らず、URLキーのような単純な方式に切り替えると、動作の予測がしやすくなる
getService().getUrl()は、実行しているアカウントによって返り値の形式が変わることがある。正規化してから使う方が安全
もっと詳しく知りたい方へ
この手の「公開直前に気づいて直した」実測は、note側でも記録しています。MISSION LOGシリーズにまとめています。
注記
この記事の内容は、自分が運営しているサービスの技術検証を一般化したものです。特定の組織・業務に関する情報は含んでいません。