使用SessionCookie调用Firebase可调用云函数遭遇403未认证错误
问题描述
用户登录时成功获取SessionCookie,但调用Firebase可调用云函数时收到403错误,提示请求未认证,同时伴随CORS错误。
技术栈
- Firebase + Next.js 13(基于
app/目录) - 登录流程按官方文档实现,颁发SessionCookie,API端点托管在Vercel
认证流程代码
客户端登录函数
signInWithEmailAndPassword(auth, data.email, data.password) .then(async (userCredential) => { const { user } = userCredential // 获取用户ID Token并发送至sessionLogin端点以设置SessionCookie return user.getIdToken() .then(idToken => { return fetch('/api/sessionLogin', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ idToken }) }) }) })
服务器端sessionLogin API端点
import { NextResponse } from "next/server" import { adminAuth } from "../../../../firebase/firebaseAdmin" export async function POST(request) { // 获取传入的ID Token const body = await request.json() const idToken = body.idToken.toString() // 设置Session有效期为5天 const expiresIn = 60 * 60 * 24 * 5 * 1000 return adminAuth .createSessionCookie(idToken, { expiresIn }) .then( (sessionCookie) => { // 初始化响应 const response = new NextResponse(null, { status: 200, statusText: "OK" }) // 设置SessionCookie的Cookie策略 response.cookies.set({ name: "sessionCookie", value: sessionCookie, maxAge: expiresIn, sameSite: "lax", httpOnly: true, secure: true, path: "/" }) return response }, (error) => { const response = new NextResponse(null, { status: 401, statusText: "UNAUTHORIZED REQUEST!" }) return response } ) }
可调用云函数代码
客户端调用
// 客户端Header.js文件 // ...(导入及初始化代码) const createPost = httpsCallable(functions, 'createPost') createPost({ authorId: sessionCookie.uid })
云函数端
// Firebase云函数functions/index.js文件 // ...(导入及初始化代码) initializeApp() setGlobalOptions({ region: "us-central1" }) export const createPost = onCall({ cors: true }, async (request) => { ... })
错误表现
- 预检请求返回403,实际请求触发CORS错误,提示无
Access-Control-Allow-Origin头 - 云函数日志显示:
The request was not authenticated. Either allow unauthenticated invocations or set the proper Authorization header. - 本地模拟器无此问题
- 与官方文档差异:未使用csrfToken,未设置auth persistence为
NONE(需保留客户端认证状态)
解决方案
核心原因
Firebase可调用云函数默认不会自动读取SessionCookie完成认证;CORS错误是因为认证失败时云函数提前返回,未正确注入CORS响应头。
方案一:通过Next.js API代理调用云函数(推荐)
利用Next.js API在服务器端读取SessionCookie并验证,再转发请求到云函数,避免客户端暴露敏感信息:
1. 创建代理API端点
// app/api/callCreatePost/route.js import { NextResponse } from "next/server" import { adminAuth } from "@/firebase/firebaseAdmin" export async function POST(request) { // 读取SessionCookie const sessionCookie = request.cookies.get("sessionCookie")?.value if (!sessionCookie) { return NextResponse.json({ error: "未认证" }, { status: 401 }) } try { // 验证SessionCookie合法性 const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie, true) // 调用云函数,携带认证信息 const cloudFunctionResponse = await fetch("https://us-central1-scipress-dev.cloudfunctions.net/createPost", { method: "POST", headers: { "Content-Type": "application/json", // 用自定义Token完成云函数认证 "Authorization": `Bearer ${await adminAuth.createCustomToken(decodedClaims.uid)}` }, body: JSON.stringify({ authorId: decodedClaims.uid }) }) const result = await cloudFunctionResponse.json() return NextResponse.json(result) } catch (error) { return NextResponse.json({ error: "认证失败" }, { status: 401 }) } }
2. 修改客户端调用逻辑
// 客户端Header.js fetch('/api/callCreatePost', { method: 'POST', headers: { 'Content-Type': 'application/json' } })
方案二:直接用ID Token认证云函数
若需直接从客户端调用云函数,可通过ID Token完成认证:
1. 客户端保存ID Token并携带请求头
// 登录函数修改,保存ID Token signInWithEmailAndPassword(auth, data.email, data.password) .then(async (userCredential) => { const { user } = userCredential const idToken = await user.getIdToken() // 临时存储ID Token(注意:localStorage存在XSS风险,仅作演示) localStorage.setItem('idToken', idToken) return fetch('/api/sessionLogin', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ idToken }) }) }) // 调用云函数时携带Token const idToken = localStorage.getItem('idToken') const createPost = httpsCallable(functions, 'createPost', { headers: { 'Authorization': `Bearer ${idToken}` } }) createPost({ authorId: (await auth.currentUser).uid })
2. 云函数验证ID Token
import { https } from "firebase-functions/v2" import { getAuth } from "firebase-admin/auth" export const createPost = onCall({ cors: true }, async (request) => { // 优先使用Firebase自动解析的认证信息,否则手动从请求头提取 let authUser = request.auth if (!authUser) { const authHeader = request.rawRequest.headers.authorization if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new https.HttpsError('unauthenticated', '用户未认证') } const idToken = authHeader.split('Bearer ')[1] const decodedToken = await getAuth().verifyIdToken(idToken) authUser = { uid: decodedToken.uid } } // 业务逻辑:使用authUser.uid处理请求 // ... })
方案三:修复CORS配置
确保云函数无论成功/失败都返回正确的CORS头,可指定允许的域名:
export const createPost = onCall({ cors: { origin: ["http://localhost:3000", "你的生产域名"], methods: ["POST"] } }, async (request) => { try { // 认证与业务逻辑 // ... } catch (error) { // 抛出标准HttpsError,自动携带CORS头 throw new https.HttpsError(error.code || 'unknown', error.message) } })
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

