Google Apps Scriptからのアクセスを雑に認証する

Google Apps ScriptからプライベートなサービスのAPIを呼び出す場合、何らかの認証が必要になるので、GASから取得したOAuth2アクセストークンを検証するプロキシサーバを作ってみた。

github.com

仕組み

ScriptApp.getOAuthToken()を使うと有効なユーザーの OAuth 2.0 アクセス トークンを取得できる。※appscript.jsonの修正が必要

// 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");
  // ...