Google Apps ScriptからプライベートなサービスのAPIを呼び出す場合、何らかの認証が必要になるので、GASから取得したOAuth2アクセストークンを検証するプロキシサーバを作ってみた。
github.com
仕組み
ScriptApp.getOAuthToken()を使うと有効なユーザーの OAuth 2.0 アクセス トークンを取得できる。※appscript.jsonの修正が必要

{
"timeZone": "Asia/Tokyo",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"openid",
"email",
"https://www.googleapis.com/auth/script.external_request"
]
}
トークンを使ってさらにユーザー情報を取得できる。
$ curl -H "Authorization: Bearer ${TOKEN}" https://openidconnect.googleapis.com/v1/userinfo
{
"sub": "...",
"picture": "...,
"email": "sugawara@winebarrel.jp",
"email_verified": true,
"hd": "winebarrel.jp"
}
作成したプロキシサーバではユーザー情報のemailが許可されているかを検証する。
使い方
$ docker run --rm ghcr.io/winebarrel/gap --help
Usage: gap --backend=BACKEND --port=UINT --header-name=STRING --allow-list=ALLOW-LIST,... [flags]
Flags:
-h, --help Show help.
-b, --backend=BACKEND Backend URL ($GAP_BACKEND).
-p, --port=UINT Listening port ($GAP_PORT).
-n, --header-name=STRING Header name to pass the access token
($GAP_HEADER).
-e, --allow-list=ALLOW-LIST,...
Allowed email list that may contain wildcards
($GAP_ALLOW_LIST).
--version
バックエンドのURL、リッスンポート、アクセストークンを渡すヘッダ名、許可するメールアドレスを指定してプロキシサーバを起動する。
$ docker run --rm -p 8080:8080 ghcr.io/winebarrel/gap -b https://example.com -e '*@winebarrel.jp' -p 8080 -n x-my-gap-token
正しいアクセストークンを渡すとプロキシサーバからバックエンドにアクセスできる。
$ curl -s -H "x-my-gap-token: ${TOKEN}" localhost:8080
<!doctype html>
<html>
<head>
<title>Example Domain</title>
間違ったアクセストークンを渡すとはじかれる。
$ curl -s -H "x-my-gap-token: ${TOKEN}x" localhost:8080
forbidden
GASからはUrlFetchApp.fetch()でアクセスできる。
function myFunction() {
const token = ScriptApp.getOAuthToken();
const response = UrlFetchApp.fetch('https://api.my-private.example.com/foo/bar/zoo', {
headers: {
"x-my-gap-token": token
}
});
console.log(response.getContentText());
}
GASをスプレッドシートのカスタム関数として使っている場合、関数から直接アクセストークンを取得することはできないが、メニューのアイテムとしてScriptApp.getOAuthToken()を呼び出してキャッシュに保存することで、カスタム関数内でアクセストークンを使うことができる。
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("認証").addItem("認証", "auth").addToUi();
}
function auth() {
const cache = CacheService.getUserCache();
const token = ScriptApp.getOAuthToken();
cache.put("token", token);
}
function myFunction() {
const cache = CacheService.getUserCache();
const token = cache.get("token");
// ...
