Firebase onCall云函数返回UNAUTHENTICATED错误,客户端Auth与App Check配置均有效
我之前排查过几乎一模一样的问题,结合你已经做的排查步骤,大概率是几个容易被忽略的细节导致的,你可以按以下顺序逐一验证:
1. 确认云函数v2的身份验证逻辑配置
你用的是Firebase Functions v2的onCall,它的身份验证机制和v1有一些细微差异。虽然你试过设置enforceAppCheck: false,但还是要检查函数的初始化选项里有没有意外覆盖了身份验证的默认行为:
export const deductServiceFee = onCall({ // 确保没有设置类似authDisabled: true的选项 }, async (request) => { // 你的函数逻辑 });
另外,升级到v2后,有没有在firebase.json里给函数配置了特殊的部署规则?比如有没有设置serviceAccount或者其他可能影响身份验证的参数?
2. 验证客户端调用云函数的时机与方式
你已经手动刷新了ID Token,但要确保调用云函数的代码是在getIdToken(true)的await之后执行,比如:
// 先确保Token刷新完成 await FirebaseAuth.instance.currentUser?.getIdToken(true); // 再调用云函数 final callable = FirebaseFunctions.instance.httpsCallable('deductServiceFee'); try { final result = await callable(); } catch (e) { // 错误处理 }
如果刷新Token和调用函数的逻辑不在同一个异步流里,可能会出现Token还没刷新完成就发起请求的情况。另外,一定要用官方的httpsCallable方法调用,不要自定义HTTP请求——因为onCall函数会自动处理ID Token的附加和验证,自定义请求很容易漏掉Authorization头。
3. 检查客户端与云函数的区域是否匹配
如果你的云函数部署时指定了非默认区域(比如europe-west1),但客户端没有对应配置,会导致请求无法正确传递身份信息。比如客户端需要这样配置:
// 在初始化Firebase后设置函数区域 FirebaseFunctions.instance.useFunctionsEmulator('localhost', 5001); // 模拟器场景 // 或者生产环境指定区域 FirebaseFunctions.instance.region = 'europe-west1';
要确保客户端指定的区域和云函数部署的区域完全一致。
4. 查看云函数的详细日志,定位Token传递问题
你目前只看到自己抛出的错误,建议在函数里添加日志,打印请求的Authorization头和auth对象:
export const deductServiceFee = onCall(async (request) => { console.log('Received Authorization header:', request.headers.authorization); console.log('Parsed request.auth:', request.auth); if (!request.auth) { throw new HttpsError("unauthenticated", "User is not authenticated."); } // ... 剩余逻辑 });
然后去Firebase控制台的云函数日志里查看:
- 如果没有Authorization头:说明客户端没有传递Token,问题在客户端的调用逻辑或Auth配置
- 如果有Authorization头,但
request.auth还是null:说明Token验证失败,可能是Token无效(比如过期、受众不匹配),或者云函数所在项目和客户端项目不匹配
5. 验证ID Token的有效性
在客户端打印刷新后的Token,然后去jwt.io解析,检查几个关键字段:
aud(受众):应该是你的Firebase项目IDiss(发行者):应该是https://securetoken.google.com/你的项目IDexp(过期时间):确保当前时间在过期时间之前
如果这些字段有问题,说明Token本身无效,可能是客户端的Firebase配置错误(比如google-services.json里的项目ID不对)。
6. 检查Identity Platform(GCIP)的集成配置
因为你升级到了GCIP,要确保在GCIP控制台里已经启用了Firebase Auth作为身份提供者,并且没有设置自定义的Token验证规则。另外,检查GCIP的“会话管理”设置,有没有把Token的有效期设置得过短,导致Token在传递到云函数时已经过期。
我当时的问题是客户端和云函数的区域不匹配,导致Token没有被正确验证,调整区域后就解决了。你可以先从日志和区域匹配这两点入手,应该能快速定位问题。
内容来源于stack exchange

