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.js
でadmin.initializeApp()
を呼び出し済み
- Functions用の
- Node v8
実クラウド上では動作するがエミュレーターでは動作しないコード
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(的ななにか)の上で動いているんだから参照可能だけどローカルでは参照できるわけないだろヴォケ、です。
解決策
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側に生えてるのかしら?