You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Cloud Functions第二代onCall方法中request.auth为何在SSR页面未定义?

问题

在Firebase函数模拟器中调用第二代Cloud Functions的onCall方法时遇到异常:SSR页面首次调用时request.auth为undefined,后续调用却能正常获取。相关代码如下:

服务端代码(/functions/getStory.js)

import { getFirestore } from 'firebase-admin/firestore'
import { error, log } from 'firebase-functions/logger'
import { onCall, HttpsError } from 'firebase-functions/v2/https'

export const getStory = onCall({ cors: true }, async (request) => {
  const db = getFirestore() // v2版本获取Firestore实例的方式

  log('request.auth from client:', request.auth)
  if (!request.auth) {
    throw new HttpsError('unauthenticated', 'Request not authenticated')
  }

  return {
    uid: request.auth.uid,
    docId: request.data.docId,
    text: 'Hello World from Server!'
  }
})

函数执行日志显示首次调用时request.auth为undefined,后续调用正常。

客户端代码(/pages/test-oncall.vue)

<template>
    <section>
        {{ response }}
    </section>
</template>

<script setup>
import { httpsCallable } from 'firebase/functions'

const response = ref('')

const { functions } = useFirebase()
const instance = httpsCallable(functions, 'getStory')
try {
    response.value = await instance({ uid: 'uidTest', docId: 'docIdTest' })
    console.warn('Response ===>', response.value)
} catch (error) {
    console.log('Error ===>', error)
}
</script>

浏览器日志报错:[FirebaseError: INTERNAL]。

想知道:

  1. 为何首次调用request.auth未定义?
  2. 如何在onCall方法中正确检查request.auth,实现服务端身份验证以判断用户是否有权限获取Firestore机密数据?

回答

一、首次调用request.auth为undefined的原因

  • SSR环境Auth状态未同步:SSR页面渲染时,客户端Firebase Auth的会话还没完成初始化或同步到请求上下文。首次调用onCall时,认证令牌没被正确附加到请求里,导致服务端识别不到用户身份;后续调用时Auth会话已就绪,令牌能正常传递,request.auth就正常了。
  • 模拟器冷启动延迟:Firebase函数模拟器首次启动或冷启动时,存在认证令牌加载延迟,第一个请求没法正确携带身份信息。

二、正确实现onCall身份验证及权限检查

  1. 客户端等待Auth就绪后再调用函数
    SSR场景下必须等Firebase Auth完成初始化、确认用户登录状态后,再发起onCall请求,避免因身份未就绪导致的空令牌问题。修改客户端代码:
<script setup>
import { httpsCallable } from 'firebase/functions'
import { getAuth, onAuthStateChanged } from 'firebase/auth'

const response = ref('')
const { functions } = useFirebase()
const auth = getAuth()

// 监听Auth状态,确认用户登录后再调用函数
onAuthStateChanged(auth, async (user) => {
  if (user) {
    const instance = httpsCallable(functions, 'getStory')
    try {
      // 不需要手动传uid,onCall会自动通过Auth令牌获取
      response.value = await instance({ docId: 'docIdTest' })
      console.warn('Response ===>', response.value)
    } catch (error) {
      console.log('Error ===>', error)
    }
  } else {
    console.log('用户未登录,请先登录')
  }
})
</script>
  1. 服务端增强身份验证与权限校验
    除了基础的request.auth存在性检查,还可以添加用户有效性、资源访问权限的校验,确保只有合法用户能获取机密数据:
export const getStory = onCall({ cors: true }, async (request) => {
  const db = getFirestore()

  // 第一步:检查是否存在有效身份
  if (!request.auth) {
    throw new HttpsError('unauthenticated', '请先登录')
  }

  const { uid } = request.auth
  const { docId } = request.data

  // 第二步:验证用户是否在系统中存在(按需)
  const userDoc = await db.collection('users').doc(uid).get()
  if (!userDoc.exists) {
    throw new HttpsError('permission-denied', '用户不存在')
  }

  // 第三步:检查用户是否有权限访问目标资源
  const storyDoc = await db.collection('stories').doc(docId).get()
  if (!storyDoc.exists || storyDoc.data().ownerUid !== uid) {
    throw new HttpsError('permission-denied', '无权访问该资源')
  }

  return {
    uid,
    docId,
    text: storyDoc.data().content || 'Hello World from Server!'
  }
})
  1. 模拟器调试注意事项
  • 确保firebase.json中正确配置Auth模拟器端口,让函数模拟器能关联身份认证服务:
{
  "emulators": {
    "auth": {
      "port": 9099
    },
    "functions": {
      "port": 5001
    },
    "firestore": {
      "port": 8080
    }
  }
}
  • 首次启动模拟器后,先在客户端完成登录操作,再发起函数调用,避开冷启动的令牌加载延迟。

内容的提问来源于stack exchange,提问作者redshift

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 01:21:16