PhpStormと僕

日々周りを巻き込むことをモットーに。気まぐれでJetBrains製のIDEネタとか書いてます。

Cloud Functionsトリガー実行時にFirestoreに認証情報を渡す

firestore.ruleで request.auth を使って「レコードは自身の保持しているデータしか操作できない」ような制約を掛けるケースはよくあると思います。

match /profiles/{uid} {
  allow read, create, update if resource.data.userUid == request.auth.uid;
}

普通に操作する分には問題ないんですが、例えばCloud Functionsの認証トリガーを利用して「ユーザが更新されたら、ユーザに紐づくプロフィール情報(Document)を更新したい」ようなケース。

exports.updateExternalProfile = functions.auth.user().onUpdate(event => {
    const user = event.data;
    const email = user.email; 
    const displayName = user.displayName; 

    db.collection('profiles').doc(user.email).update({ displayName })
});

そのまま更新すれば良いように思えますが、Cloud Functions内でのこの外部トリガーからの実行処理中はユーザログイン情報がないため、つまり request.auth がないので更新しようとすると権限エラーになります。 Cloud Functions + Firestoreを使っていると苦しむこの問題。どうするか。

github.com

Issuesも上がっていますが、2018年03月時点ではまだfirebaseAdminでは解決していない模様。ちなみにRealtime Databaseは同様のことを満たすための databaseAuthVariableOverride っていうオプションがあるみたいですね。


ではどうすれば良いか。 認証情報を冗長に持つ必要があるものの、firebase-admin-node に加えて firebase-js-sdk を使って、signInWithCustomTokenメソッドを使ってログイン状態にさせると一応動くようになります。

const admin = require('firebase-admin');
const serviceAccount = require('/path/to/serviceAccount.json');
admin.initializeApp({
  credential : admin.credential.cert(serviceAccount),
});

// 追加処理
const firebase = require('firebase');
require('firebase/auth');
require('firebase/firestore');
firebase.initializeApp({
  // webAppの設定
});

const signIn = async (userUid) => {
  const customToken = await admin.auth().createCustomToken(userUid)
  return firebase.auth().signInWithCustomToken(customToken)
}

exports.updateExternalProfile = functions.auth.user().onUpdate(async event => {
  const user = event.data;
  const email = user.email;
  const displayName = user.displayName;

  // 追加処理
  await signIn(email)

  db.collection('profiles').doc(user.email).update({ displayName })
});

こんな感じ。 認証周りの冗長感が否めないので、早くfirebaseAdmin側で対応してくれないかなー。