Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9229

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

$
0
0

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側に生えてるのかしら?


Viewing all articles
Browse latest Browse all 9229