Google Apps Script DE OAuth2 ~ GASで直接触れない Google APIを叩いてみるお~


GASをガスって読むのは中の人からするとあまり好きじゃないらしいですね。大橋です。

TypetalkAppの時も少し言及したかもですが、
最近GASでOAuth2がやりやすくなりました。

ただし
やりやすくなったと言っても、
UrlFetchAppがOAuth1ように備えていたような、
自動でほぼほぼやってくれるみたいなレベルでは無いのですが、
それでもかなり楽になったのは確かです。

ただはっきりした話がまだどこにも書いてない気がするので
今回はその話を書きたいと思います。

# おち

GASでOAuth2をやる場合は`ScriptApp.newStateToken()`を使って、`/usercallback`に`state`パラメータで渡す

# 準備

いかがGASでOAuth2する上で必要なものになります。

* ClientId/ClientSecret
    * Service Provider(authorization server)で取得して下さい。


今回はGoogleのAPIを叩くので、[Developers Console](https://console.developers.google.com/project)から取得して下さい。
ただたいていのService ProviderではClientIdやCilentSecretを取得する際に`redirect_uri`の設定が必要になります。

細かい説明は後半でしますが、`redirect_uri`にはGASをWebアプリケーション化した際に利用できるURLの末尾を`/usercallback`にしたものを登録して下さい。

例:
**GASのWEBアプリケーションURL**

```
https://script.google.com/macros/s/ABCDEFG-abcdefg-123456789/dev
```
↓

**登録するredirect_uri**

```
https://script.google.com/macros/s/ABCDEFG-abcdefg-123456789/usrcallback
```

# 詳細

大きく分けて2つの処理が必要です。

1. Service Provider(今回はGoogle)の認可画面へユーザを遷移させるためのリンクを表示する画面を表示する処理(大体はdoGet)
     * もっと言うと 認可画面へユーザを遷移させるためのURLを作る処理
2. Service Providerから認可後戻ってきた際に処理を行うcallback処理
     * この中では認可コードを元にAccessTokenを取得する。




認可画面へユーザを遷移させるためのURLを作るには、
認可画面へ言った後、GASのWebアプリケーションに戻ってきて何かしらの処理をする必要があります。
OAuth2であれば大体の場合は認可コードが渡されるのでそれを元にAccessTokenを取得するとかですね。

この何かしらの処理は自身で作成する必要があるのですが、それをdoGetの中に書いてしまうと、色々複雑になりますよね。
なので、GASでは先ほど`redirect_uri`で登録したような
`/usercallback`という他のアプリケーションから戻ってきた際に処理を行うようのURLがあります。

`/usercallback`はGASで用意されている特別なURIでとある`state`と呼ばれるパラメータと一緒に利用すると、
そのパラメータを元にGAS中に記載された任意の関数を呼び出すことが可能です。

ここでこのとあるパラメータを作るのに必要になるのが、`ScriptApp.newStateToken()`から返却される`StateTokenBuilder`です。

StateTokenBuilderは`/usercallback`でどのような処理をさせるかを記述した文字列を作成することができるサービスで、
以下の様なメソッドを持っています。
 
* StateTokenBuilder.withMethod(method)
    * `/usercallback`に遷移後呼び出される関数名を文字列で指定します。
    * 関数内でHtmlServiceやUiAppを返却するとそのままUIを表示できます。
* StateTokenBuilder.withTimeout(seconds)
    * このstateパラメータの生存期間を設定できます。設定値は秒です。
* StateTokenBuilder.withArgument(name, value)
    * withMethodで指定した関数に渡す引数を設定できます。
    * withMethodで設定された関数(コールバック関数)には`doGet`同様にパラメータが渡されます。仮にコールバック関数でeという引数をとり、withArgumentでname=hoge, value=fugaと設定した場合、`e.parameter.hoge`で`fuga`が取得できます。


上記を用いて、GoogleのOAuth2用URLを作り、かつそのリンクをHTMLServiceを使って表示すると以下の様な感じになります。


```js
function doGet() {
  //clientIdの保存用
  var prop = PropertiesService.getScriptProperties().getProperties();

  //GoogleでOAuth2する場合のエントリーポイント
  var url = "https://accounts.google.com/o/oauth2/auth";

  //必要なパラメータ郡
  var param = {
    "response_type" : "code",
    "client_id" : prop.clientId,
    "redirect_uri" : getCallbackURL(),  //←は↓に書いてあります
    "state" : ScriptApp.newStateToken().withMethod("callback").withArgument("name", "value").withTimeout(2000).createToken()  //←の指定で/usercallbackが呼び出された後、callback関数を呼び出し、その際nameというパラメータでvalueという値を渡し 2000秒でタイムアウトになるという設定が可能になります。
    "scope" : "https://www.googleapis.com/auth/compute",
    "access_type" : "offline"
  };
  var params = [];
  for(var name in param){ 
    params.push(name + "=" + encodeURIComponent(param[name]));
  }
  
  url = url + "?" + params.join("&");
  
  return HtmlService.createHtmlOutput('<a href="' + url + '">認証</a>')  
  
}



function getCallbackURL() {
  // https://script.google.com/****/usercallbackを作成します。
  var url = ScriptApp.getService().getUrl();
  if (url.indexOf("/exec") >= 0) {
    return url.slice(0, -4) + 'usercallback';
  }
  
  return url.slice(0, -3) + 'usercallback';

}


function callback(e) {
  return HtmlService.createHtmlOutput("<pre>" + JSON.stringify(e, "",2) + "</pre>");
  
  //実際にAccessTokenを取得する場合は以下の様な感じになります。
//  var code = e.parameter.code;
//  var credentials = fetchAccessToken(code);
//  var properties = PropertiesService.getScriptProperties();
//  properties.setProperty("credentials", JSON.stringify(credentials));
//  var prop = properties.getProperties();
//  var res = UrlFetchApp.fetch("https://www.googleapis.com/compute/v1/projects/" + prop.projectId , {
//                              headers : {
//                                "Authorization": "Bearer " + credentials.access_token
//                              }
//                              
//                              });
//  
//  
//  
//  
//  return HtmlService.createHtmlOutput("<pre>" + res.getContentText() + "</pre>");
}

function fetchAccessToken(code) {
  var prop = PropertiesService.getScriptProperties().getProperties();
  var res = UrlFetchApp.fetch("https://accounts.google.com/o/oauth2/token", {
    "method" : "POST",
    payload : {
      "code" : code,
      "client_id" : prop.clientId,
      "client_secret" : prop.clientSecret,
      "redirect_uri" : getCallbackURL(),
      "grant_type" : "authorization_code"
    },
    muteHttpExceptions : true
  });
  
  return JSON.parse(res.getContentText());
}

```

# まとめ

ということでざっくりですがStateTokenBuilderの使い方について書きました。
これを利用すればFacebookAPIとかもかなり簡単にAccessTokenが取得可能になります。

※後半疲れてふっ飛ばしました...