1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Firebase FunctionsのエミュレーターでもGoogle API用のアクセストークンを得る方法

Posted at

Firebase、いつの間にかとても練れた内容になってきてました。
というわけでバリバリ使っておりますが、たまにドハマリし、かつ英語で検索してすら情報ほぼゼロ、ということがあります。
そんな中の「え、これでいいの?」をご紹介します。
今回の内容はタイトルそのまんま、です。

概要

  • Firebase Cloud Functions内で、Google API用のアクセストークンを得たい
    • クライアントサイドでFirebase使いまくりのSPAがあり、そのSPA内からGoogle APIサービスを叩きたい
    • が、SPA内にAPIキーを埋め込みたくない
    • ので、アクセストークンをFunctions内で得、それをSPAに渡したい
  • 超素朴実装をすると、デプロイした実クラウド上ではちゃんと動作するが、ローカルPC上で動作させたエミュレーター上では動作しない
  • ものすごく簡単な解決方法があるが、あまりネット上には出ていない模様

レギュレーション

  • Firebaseプロジェクト作成済み
    • 管理者認証情報作成済み&配置済み&環境変数で指定済み
    • プロジェクトに紐付いた、利用したいGoogle APIを有効化済み
  • Firebase Cloud Functions およびローカルPC上のエミュレーター
    • Functions用の index.jsadmin.initializeApp() を呼び出し済み
  • Node v8

実クラウド上では動作するがエミュレーターでは動作しないコード

index.js
exports.getAccessToken = functions.https.onCall(async (data, context) => {
  // カスタムクレームによる権限認証をここで行う
  try {
    const response = JSON.parse(await request({
      url: 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token?scopes=https://www.googleapis.com/auth/cloud-platform',
      headers: {
        'Metadata-Flavor': 'Google'
      }
    }));
    if (!response.access_token) {
      throw new Error('bad response ' + JSON.stringify(response || {}));
    }
    return response;
  } catch (e) {
    throw new functions.https.HttpsError('aborted', e.message);
  }
);

要は Google CloudのIDとアクセストークンの取得 に出ているのをそのままNodeに翻訳しただけです。そしてこれで、実クラウドではきちんと動きます。

しかし、エミュレーター上ではこれは動作しません。

getaddrinfo ENOTFOUND metadata.google.intenral

…そりゃするわけないわ! http://metadata.google.internal/ ってどんなFQDNだよ!! っていう単純な話です。これは有名なメタデータサーバー、GCEインスタンスから参照できるアドレスであり、 FunctionsだってGCE(的ななにか)の上で動いているんだから参照可能だけどローカルでは参照できるわけないだろヴォケ、です。

解決策

index.js
exports.getAccessToken = functions.https.onCall(async (data, context) => {
  // カスタムクレームによる権限認証をここで行う
  try {
    return await admin.app().options.credential.getAccessToken();
  } catch (e) {
    throw new functions.https.HttpsError('aborted', e.message);
  }
);

え、これでいいの…? というワンライナーになってしまいました(^^ゞ

実はFirebase Admin SDKの admin.credential.Credential クラスに getAccessToken っていうそのまんまのメソッドが生えていて、これを呼び出せば、先述の実クラウドで動作するものと同じフォーマットのJSONが得られます。

そして、管理者認証情報つきで初期化済みのAdmin SDKの App インスタンスは、options プロパティーの中に credential プロパティーを抱えているわけです。

謎(調べてないだけ)

しかしこれ、Functionsエミュレーターはどうやってエミュレートしてるんですかね、メタデータサーバーを…何か秘密の口がFirebase側に生えてるのかしら?

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?