88259


### 前提

GASを使用  
ウェブアプリケーションのデプロイ設定を下記で行いました。

**・次のユーザーとして実行**  
**ウェブアプリケーションにアクセスしているユーザ**

**・アクセスできるユーザ**  
**組織 内の全員**

### 実現したいこと

htmlファイルに記載したjavascriptから、デプロイを行なったGASの結果を取得したです。

また、実行・アクセスできるユーザは組織内のユーザのみに限定したいので、  
GASデプロイ設定の「次のユーザーとして実行」と「アクセスできるユーザ」は上記の設定でお願いしたいです。

### 発生している問題・エラーメッセージ

上記の設定にすると処理結果を受け取れてません。

chromeのコンソールで確認するとこのような中身になってました。

[code]

    Response {type: 'opaque', url: '', redirected: false, status: 0, ok: false, …}
    body: (...)
    bodyUsed: false
    headers: Headers {}
    ok: false
    redirected: false
    status: 0
    statusText: ""
    type: "opaque"
    url: ""
    [[Prototype]]: Response
    
    戻り値: 
    
[/code]

### 該当のソースコード

GASのコード

[code]

    function doGet(e) {
    
      let json = {
        'text': 'aaaa'
      }
      let res = JSON.stringify(json)
      return ContentService.createTextOutput(res).setMimeType(ContentService.MimeType.TEXT)
    }
    
[/code]

HTMLのコード

[code]

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
        </head>
        <body>
        </body>
        <script>
            let url = 'https://script.google.com/macros/s/****/exec';
    
            let option = {
                'mode': 'no-cors'
                , 'method': 'GET'
                , 'Content-Type': 'text/plain'
            };
    
            let text = '';
            fetch(url, option)
            .then(
                function(res) {
                    console.log(res);
                    return res.text();
                }
            ).then(
                function(json) {
                    text = json;
                    console.log("戻り値:" + json);
                }
            ).catch(function(exception) {
                console.log(exception);
            });
        </script>
    </html>
    
[/code]

### 試したこと

GASのデプロイ設定を下記のようにすると問題ないことは確認できました。

**・次のユーザーとして実行**  
**自分**

**・アクセスできるユーザ**  
**全員**

HTMLに記載のoption変数を下記に変更

[code]

            let option = {
                'method': 'GET'
                , 'Content-Type': 'text/plain'
            };
    
[/code]

chromeのコンソール

[code]

    Response {type: 'cors', url: 'https://script.googleusercontent.com/macros/echo?u…mZixddpbUVg&lib=Mv9wYkUhvGHhGDLT7HCgnKd1sAI6oo1Au', redirected: true, status: 200, ok: true, …}
    body: (...)
    bodyUsed: true
    headers: Headers {}
    ok: true
    redirected: true
    status: 200
    statusText: ""
    type: "cors"
    url: "https://script.googleusercontent.com/macros/echo?user_content_key=t6AwjjspV1kU-_1qlO63Z70XrFMres0fI6ZSpURhXch6TbNOgrYKmA3bFlVl5B6MYfaE8wqe2DRC_OgKWl0SejQeQtPVB4Pgm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnPBIAyb9Xw1qqs9Rjqalb0gct2gCt1kEk1G9y721k1e5r_xTxoPnJWNKJQ7J6HlSX7MjKYTMKDczKxY3e265L2RmZixddpbUVg&lib=Mv9wYkUhvGHhGDLT7HCgnKd1sAI6oo1Au"
    [[Prototype]]: Response
    戻り値:{"text":"aaaa"}
    
[/code]

しかし、この設定だと  
**Urlを知っていると誰でも実行可能で危険**  
なのでこの設定は避けたいです。

fetchのinit引数にcredentials と下記の3つを一つずつ試しましたが結果は変わらずでした。

[code]

    'include'
    'same-origin'
    'omit'
    
[/code]

* * *

ScriptApp.getOAuthToken() で値を取得し  
HTMLファイルに反映させましたが、結果は変わらずでした。

ScriptApp.getOAuthToken()の値を取得し、設定した方法

  1. ダミー関数をGASのエディタから実行して、コンソールに表示された値をコピー
  2. 1 でコピーした値をHTMLファイルの oAuthToken の値にセット

[code]

    // ダミー関数
    function myFunction() {
      console.log(ScriptApp.getOAuthToken())
    }
    
    
[/code]

GASでのスコープ設定

[code]

      "oauthScopes": [
        "https://www.googleapis.com/auth/script.external_request",
        "https://www.googleapis.com/auth/userinfo.email",
        "https://www.googleapis.com/auth/drive.file",
        "https://www.googleapis.com/auth/drive",
        "https://www.googleapis.com/auth/drive.apps.readonly",
        "https://www.googleapis.com/auth/drive.readonly",
        "https://www.googleapis.com/auth/drive.appfolder",
        "https://www.googleapis.com/auth/drive.scripts"
      ]
    
[/code]

[code]

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
        </head>
        <body>
        </body>
        <script>
            let url = 'https://script.google.com/macros/s/***/exec';
    
            let oAuthToken = 'ya29.A0ARrdaM-b4Qkdt970ib90xUfoXIsCqkJYZWyg1KnJw04kIBlFvSZ1QwaP-u_ed5BbIamgpT6p-MWqYPoY5RtMWvXXt06DqmeIE6C2cromOec2WSlY4wQanAvAhLpoGAoZKf80377UACfIuoAGLqV8ks8X-MVpnKn5DO7icpB6KxSfhDI';
    
            let option = {
                'mode': 'no-cors'
                , 'method': 'GET'
                , 'Content-Type': 'text/plain'
                , 'headers': {
                    "Authorization": "Bearer " + oAuthToken
                }
            };
    
            let text = '';
            fetch(url, option)
            .then(
                function(res) {
                    console.log(res);
                    return res.text();
                }
            ).then(
                function(json) {
                    text = json;
                    console.log("戻り値:" + json);
                }
            ).catch(function(exception) {
                console.log(exception);
            });
        </script>
    </html>
    
[/code]

[![画像の説明をここに入力](https://i.stack.imgur.com/Mr0F6.png)](https://i.stack.imgur.com/Mr0F6.png)

* * *

`'mode': 'no-cors'` を外して実行

GASは変更なし

[code]

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
        </head>
        <body>
        </body>
        <script>
            let url = 'https://script.google.com/macros/s/AKfycbx3DG-O0pFRLzpTxqM7cgkF8bMvNvsCYU97upqK9P7G-T2NuAzHyQ5iaPuHY_1SnMNm/exec';
    
            let oAuthToken = 'ya29.A0ARrdaM_zEoVPRmZHqKDUC06EdbXc7PzhJEW1IjYknNyjSpQI0_mfHbEVwQaSFeYJuIccwmBKJn7mCZ9JCINlswjtLIRxM-7mSMey-tGAVYG78Z4MSmLihutuyrqFZxgvcPKmesrSvRqIYSgMTLUY6lFZJX855PaBSOZD8pSlgL4Z4uYD';
    
            let option = {
                // 'mode': 'no-cors'
                /*,*/ 'method': 'GET'
                , 'Content-Type': 'text/plain'
                , 'headers': {
                    "Authorization": "Bearer " + oAuthToken
                }
            };
    
            let text = '';
            fetch(url, option)
            .then(
                function(res) {
                    console.log(res);
                    return res.text();
                }
            ).then(
                function(json) {
                    text = json;
                    console.log("戻り値:" + json);
                }
            ).catch(function(exception) {
                console.log(exception);
            });
        </script>
    </html>
    
[/code]

[![画像の説明をここに入力](https://i.stack.imgur.com/I5YVz.png)](https://i.stack.imgur.com/I5YVz.png)

* * *

curlでの実行

[code]

    $ curl -L "https://script.google.com/macros/s/AKfycbx3DG-O0pFRLzpTxqM7cgkF8bMvNvsCYU97upqK9P7G-T2NuAzHyQ5iaPuHY_1SnMNm/exec" -H "Authorization: Bearer ya29.A0ARrdaM_zEoVPRmZHqKDUC06EdbXc7PzhJEW1IjYknNyjSpQI0_mfHbEVwQaSFeYJuIccwmBKJn7mCZ9JCINlswjtLIRxM-7mSMey-tGAVYG78Z4MSmLihutuyrqFZxgvcPKmesrSvRqIYSgMTLUY6lFZJX855PaBSOZD8pSlgL4Z4uYD"
    {"text":"aaaa"}
    
[/code]


CC BY-SA 4.0
2022-03-29T04:35:12.927
88083
2022-04-21T03:31:54.617
2022-03-31T08:54:50.443
52008
52008
question
javascript google-apps-script
GASのウェブアプリケーションをjsのfetchで実行する方法


自己解決しました。

  * Google OAuth 2.0 認証を使用したログイン
  * GCPのOAuth同意画面、認証情報のAPIキーとOAuth 2.0 クライアント IDの作成
  * GAS側で実行可能APIを使用  
上記をすることでjsのfetchから実行できることを確認しました。

実装したコードは以下になります。

**index.html**

[code]

    <!doctype html>
    <html lang="ja">
    
    <head>
        <meta charset="UTF-8">
    </head>
    
    <body>
        <div id="auth-div" style="display: none;">
            <span>アカウント認証していないので、認証ボタンを表示する</span>
            <button id="auth-btn" onclick="handleAuthClick(event)">認証</button>
        </div>
        <div id="exec-div" style="display: none;">
            <span>アカウント認証が問題無いので、実行ボタンを表示する</span>
            <button id="exec-btn" onclick="execution(event)">実行</button>
            <div>
                実行結果: <label id="exec-result"></label>
            </div>
        </div>
        <script defer src="https://apis.google.com/js/api.js"
            onload="this.onload=function(){};handleClientLoad()" onreadystatechange="if (this.readyState === 'complete') this.onload()"></script>
        <script src="index.js"></script>
    </body>
    </html>
    
[/code]

**index.js**

[code]

    var CLIENT_ID = 'GCPの認証情報 Auth 2.0のクライアントID';
    
    var API_KEY = 'GCPの認証情報 APIキー';
    
    var SCOPES = [
        'https://www.googleapis.com/auth/userinfo.email'
        , 'https://www.googleapis.com/auth/script.external_request'
    ];
    
    var SCRIPT_ID = 'GASの実行可能APIにある、/exec前のID';
    
    var DISCOVERY_DOCS = [
        "https://script.googleapis.com/$discovery/rest?version=v1"
    ];
    
    // 認証用div
    var authDiv = document.getElementById('auth-div');
    // 実行用div
    var execDiv = document.getElementById('exec-div');
    
    // アクセス時に実行
    function handleClientLoad() {
        gapi.load('client:auth2', initClient);
    }
    
    // 初期化処理
    function initClient() {
        // GoogleAPIの初期化
        gapi.client.init(
            {
                apiKey: API_KEY
                , discoveryDocs: DISCOVERY_DOCS
                , clientId: CLIENT_ID
                , scope: SCOPES.join(' ')
            }
        ).then(function () {
            // ログイン状態取得
            let isSignIn = gapi.auth2.getAuthInstance().isSignedIn.get();
    
            if (isSignIn) {
                // ログイン済みなら、実行用の項目表示
                execDisplay();
            } else {
                // 未ログインなら、ログイン用の項目表示
                loginDisplay();
            }
        }, function (error) {
            console.log("エラー: " + error);
        });
    }
    
    // ログイン用の項目表示
    function loginDisplay() {
        // 未ログイン
        authDiv.style.display = 'inline';
        execDiv.style.display = 'none';
    }
    
    // 実行用の項目表示
    function execDisplay() {
        // ログイン済み
        authDiv.style.display = 'none';
        execDiv.style.display = 'inline';
    }
    
    
    // 認証ボタンクリック時
    function handleAuthClick(event) {
        gapi.auth2.getAuthInstance().signIn()
            .then(function (res) {
                // ログイン認証成功
                execDisplay();
            }, function (error) {
                // ログイン認証失敗
                console.log("ログイン認証失敗: " + JSON.stringify(error, null, 2));
            });
    }
    
    // 実行ボタンクリック時
    function execution(event) {
    
        let request = {
            'function': 'exec'
            , 'parameters': []
        };
    
        let op = gapi.client.request(
            {
                'root': 'https://script.googleapis.com'
                , 'path': 'v1/scripts/' + SCRIPT_ID + ':run'
                , 'method': 'POST'
                , 'body': request
            }
        );
    
        op.execute(function (res) {
            // console.log("実行結果: " + JSON.stringify(res, null, 2));
    
            if (res.error) {
                // エラーがある場合
                if (res.error.status) {
                    console.log('API実行エラー: ' + JSON.stringify(res, null, 4));
                } else {
                    // スクリプトエラーの場合
                    let error = res.error.details[0];
                    console.log('スクリプトエラー: ' + error.errorMessage);
                }
            } else {
                let json = JSON.parse(res.response.result);
                // console.log(json);
    
                // 実行結果表示ラベル
                let execLabel = document.getElementById('exec-result');
    
                if (json.result === 'ok') {
                    execLabel.innerText = '' + '\r\n';
                    execLabel.innerText += '正常終了しました。';
                } else {
                    execLabel.innerText = '処理エラー';
                }
            }
        });
    }
    
[/code]

**index.gs**

[code]

    function exec() {
      let res = {
        "test": 123
        }
      let json = res
      return JSON.stringify(json)
    }
    
[/code]

index.html表示すると  
未ログインの時は、「認証ボタン」が表示されます。  
ログイン済みの時は、「実行ボタン」が表示されます。

「実行ボタン」をクリックすると、問題ない場合は「実行結果: 正常終了しました。」が表示されます。


CC BY-SA 4.0
2022-04-11T05:40:54.557
88259
2022-04-21T03:31:54.617
2022-04-21T03:31:54.617
52008
52008
88083
answer