エンジニアになりたい人募集!X(旧Twitter)からフォローしたらリプライで質問常時OK!

【GAS】GASで作成したWebサイトでBasic認証(もどき)を実装する

この記事はで読むことができます。

はじめに

GASで作成したWebサイトはデフォルトで「自分のみ」「Googleアカウントを持つ全員」「全員」という公開範囲を設定可能です。
さらにアクセスしているユーザの情報を取得することで細かなアクセス制御も可能ですが、Googleアカウントの情報を取得するため厳密に制限ができる反面、事前にアクセスを許可するメールアドレスをリストに追加し、閲覧ユーザにユーザ情報の取得を許可してもらう必要があります。
今回紹介する方法では事前のメールアドレス把握やユーザ情報の取得は不要です
ただし通常のbasic認証同様、IDとPWがわかればアクセスできてしまう点にはご注意ください。

この記事では以下のステップでコードを解説しています。

1. 仕組み

ID/PWの認証にはGASのProperties Serviceクラスの「スクリプトプロパティ」と「ユーザプロパティ」を使用します。
スクリプトプロパティは全てのユーザがアクセスできるプロパティ、ユーザプロパティはスクリプトを動かすユーザのみがアクセスできるプロパティです。
スクリプトプロパティへ事前にID/PWを設定しておき、ユーザプロパティへユーザが入力したID/PWを保存をします。

GET時にID/PWの認証を行い、結果に応じて認証画面と認証後の画面を出し分けます。
認証が成功した場合にユーザプロパティにID/PWを保存して、次回以降はGET時に保存した認証データを参照します。

2. コード一覧

「ファイルを追加」から以下のファイルを作成し、各々コードを書き換えてください。
※ ID/PWをコードに直接記述すると、ライブラリとして公開されるURLを利用した際に外部のユーザが取得できてしまうため、{ID} {PW}から変更しないでください

  • gas.gs
  • auth.html
  • index.html

gas.gs

サーバー側の処理部分になります

gas
function setup() {
  PropertiesService.getScriptProperties().setProperty('auth', JSON.stringify({id:"{ID}", pw:"{PW}"}));
}

function doGet() {
  const authOwn = JSON.parse(PropertiesService.getUserProperties().getProperty('auth_users'));
  if (tryAuth(authOwn) == "success"){
    var htmlOutput = HtmlService.createTemplateFromFile("index").evaluate();
  } else {
    var htmlOutput = HtmlService.createTemplateFromFile("auth").evaluate();
  }
  return htmlOutput;
}

function tryAuth(authOwn) {
  if (authOwn != null){
    const auth = JSON.parse(PropertiesService.getScriptProperties().getProperty('auth'));
    if (auth.id == authOwn.id && auth.pw == authOwn.pw) {
      PropertiesService.getUserProperties().setProperty('auth_users', JSON.stringify(authOwn));
      return "success";
    }
  }
  return "fail";
}

function deleteAuth(){
  PropertiesService.getUserProperties().deleteAllProperties();
}

auth.html

認証画面としてフロントに表示するものです

html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <form onsubmit="handleSubmit()">
        <input name="id" type="text" placeholder="ID" required="required" autocomplete="off">
        <input name="pw" type="password" placeholder="PW" required="required" autocomplete="off">
        <input type="submit" value="認証">
    </form>
    <script>
      window.addEventListener("load", function(){
        document.querySelector("form").addEventListener("submit", function (event) {
          event.preventDefault();
        });
      });
      function handleSubmit() {
        document.querySelector("input[type=submit]").disabled = true;
        const authOwn = document.querySelector("form");
        google.script.run
          .withFailureHandler(onFailure)
          .withSuccessHandler(onSuccess)
          .tryAuth(authOwn);
      }
      function onSuccess(rslt){
        if (rslt == "success"){
          window.open("<?= ScriptApp.getService().getUrl() ?>", "_top");
        } else {
          alert("認証情報が一致しません");
          document.querySelector("input[type=submit]").disabled = false;   
        } 
      }
      function onFailure(){
        alert("正常に完了しませんでした");
        document.querySelector("input[type=submit]").disabled = false;
      }
    </script>
  </body>
</html>

index.html

認証済の場合にフロントで表示するものです

html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    認証成功
    <button type="button" onclick="reset()">認証情報削除</button>
    <script>
      function reset(){
        google.script.run
          .withFailureHandler(reload)
          .withSuccessHandler(reload)
          .deleteAuth();
      }
      function reload(){
        window.open("<?= ScriptApp.getService().getUrl() ?>", "_top");
      }
    </script>

  </body>
</html>

3. セットアップ方法

GASの編集画面からsetup()を実行
開いたGoogleアカウントの権限確認画面で許可を選択
左側のタブからプロジェクトの設定を選択
画面最下部までスクロールし、「スクリプトプロパティを編集」を押下し、{ID}{PW}の部分を変更
変更後に「スクリプトプロパティを保存」を押下し、変更した内容を保存する
「デプロイ」から以下の通り設定した新しいデプロイを作成する

※ 必ず「ウェブアプリケーションにアクセスしているユーザー」で実行するように設定してください

生成されたウェブアプリURLにアクセスする

4. コード解説

※ JavaScriptの挙動については詳細は割愛しています

gas.gs

gas.gsに記述するコードについて解説します。

setup():スクリプトプロパティのデフォルト値を設定

PropertiesServiceクラスのgetScriptProperties()メソッドでスクリプトプロパティを取得
取得したプロパティに対してPropertiesクラスのsetProperty(key, value)メソッドでキーauthに対してidとpwを保存
このとき、Object型ではプロパティの値として扱えないため、JSON形式に変換を行う

gas
PropertiesService.getScriptProperties().setProperty('auth', JSON.stringify({id:"{ID}", pw:"{PW}"}));
doGet():認証結果別にフロントへ返すHTMLファイルを定義

webサイト読み込み時(GET時)に動作

PropertiesServiceクラスのgetUserProperties()メソッドでユーザプロパティを取得
取得したプロパティに対してPropertiesクラスのgetProperty(key)メソッドでキーauth_usersに紐づく値を取得
JSON形式で保存されているため、Object型に戻して変数authOwnへ格納

gas
const authOwn = JSON.parse(PropertiesService.getUserProperties().getProperty('auth_users'));

関数tryAuthへ変数authOwnで認証をリクエスト
戻り値に応じたhtmlファイルを変数htmlOutputにHtmlOutputオブジェクトとして格納
HtmlOutputオブジェクトはHtmlServiceクラスのcreateTemplateFromFile(filename)メソッドでhtmlファイルをHtmlTemplateオブジェクトに変換し、HtmlTemplateクラスのevaluate()メソッドで変換して生成
※ 今回はhtml内にGASコードが含まれるためcreateTemplateFromFile(filename)を使用

gas
if (tryAuth(authOwn) == "success"){
  var htmlOutput = HtmlService.createTemplateFromFile("index").evaluate();
} else {
  var htmlOutput = HtmlService.createTemplateFromFile("auth").evaluate();
}

フロントで出力する内容として変数htmlOutputを返す

gas
return htmlOutput;
tryAuth(authOwn) :認証を実行

変数authOwnが空でない場合、
PropertiesServiceクラスのgetScriptProperties()メソッドでスクリプトプロパティを取得
取得したプロパティに対してPropertiesクラスのgetProperty(key)メソッドでキーauthに紐づく値を取得
JSON形式で保存されているため、Object型に戻して変数authへ格納

さらに変数authOwnと変数authのid/pwがどちらも一致した場合、
PropertiesServiceクラスのgetUserProperties()メソッドでユーザープロパティを取得
取得したプロパティに対してPropertiesクラスのsetProperty(key, value)メソッドでキーauth_usersに対してidとpwを保存
このとき、Object型ではプロパティの値として扱えないため、JSON形式に変換を行う

認証結果としてsuccessを返す

gas
if (authOwn != null){
  const auth = JSON.parse(PropertiesService.getScriptProperties().getProperty('auth'));
  if (auth.id == authOwn.id && auth.pw == authOwn.pw) {
    PropertiesService.getUserProperties().setProperty('auth_users', JSON.stringify(authOwn));
    return "success";
  }
}

変数authOwnが空の場合もしくは変数authOwnと変数authのidとpwが一致しなかった場合、認証結果としてfailを返す

gas
return "fail";
deleteAuth():ユーザプロパティをクリア

PropertiesServiceクラスのgetUserProperties()メソッドでユーザープロパティを取得
取得したプロパティに対してPropertiesクラスのdeleteAllProperties()メソッドで全てのキーとそれに紐づく値を削除

gas
PropertiesService.getUserProperties().deleteAllProperties();

auth.html

auth.htmlに記述するJavascriptについて解説します。

window.addEventListener(“load”, function):イベントを追加

画面読み込み完了後に自動実行

form要素のsubmit時にsubmitのデフォルトイベントをキャンセルするイベントを追加

js
document.querySelector("form").addEventListener("submit", function (event) {
  event.preventDefault();
});
handleSubmit():GAS側へ認証をリクエスト

重複送信防止の為、submitボタンを非活性化

js
document.querySelector("input[type=submit]").disabled = true;

変数authOwnにform要素を格納

js
const authOwn = document.querySelector("form");

GAS側の関数tryAuthへ変数authOwnで認証をリクエスト
認証結果を問わず関数実行が失敗した場合、関数onFailureを動作させる
認証結果を問わず関数実行が成功した場合、関数onSuccessを動作させる

js
google.script.run
  .withFailureHandler(onFailure)
  .withSuccessHandler(onSuccess)
  .tryAuth(authOwn);
onSuccess(rslt):フロントへ返すHTMLファイルを変更するためリロード

認証が成功した場合、現在のURLに再アクセスを行いGAS側の関数doGetを動作させる
現在のURLはGASのScriptAppクラスのgetService()メソッドでウェブアプリのServiceオブジェクトを取得し、getUrl()メソッドで取得
※ GASのwebサイトはiframe構造のためJSのlocation.hrefでは取得できません

失敗した場合、認証が失敗した旨をアラートで通知し、submitボタンを活性化

js
if (rslt == "success"){
  window.open("<?= ScriptApp.getService().getUrl() ?>", "_top");
} else {   
  alert("認証情報が一致しません");
  document.querySelector("input[type=submit]").disabled = false;
} 
onFailure():関数実行失敗を通知

関数実行が失敗した旨をアラートで通知

js
alert("正常に完了しませんでした");

submitボタンを活性化

js
document.querySelector("input[type=submit]").disabled = false;

index.html

index.htmlに記述するJavascriptについて解説します。

reset():GAS側へ認証情報削除をリクエスト

GAS側の関数deleteAuthへ認証情報削除をリクエスト
関数実行結果を問わず、関数reloadを動作させる

js
google.script.run
  .withFailureHandler(reload)
  .withSuccessHandler(reload)
  .deleteAuth();
reload():フロントへ返すHTMLファイルを変更するためリロード

現在のURLに再アクセスを行いGAS側の関数doGetを動作させる
現在のURLはGASのScriptAppクラスのgetService()メソッドでウェブアプリのServiceオブジェクトを取得し、getUrl()メソッドで取得
※ GASのwebサイトはiframe構造のためJSのlocation.hrefでは取得できません

js
window.open("<?= ScriptApp.getService().getUrl() ?>", "_top");

まとめ

GASでBasic認証もどきを実装する方法を解説しました。
コピペだけで実装できますので、社内や仲間内での共有などにぜひ活用してみてください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)