我正在尝试让一个 GCP Firebase 云函数调用另一个。 我使用 https://cloud.google.com/functions/docs/secure/authenticating#authenticating_function_to_function_calls 作为指南。 我一切正常,但是当调用该函数时,它会抛出以下错误:
Failed to validate auth token. FirebaseAuthError: Firebase ID token has incorrect "aud" (audience) claim. Expected "{projectId}" but got "https://{project-region}-{projectId}.cloudfunctions.net/{functionName}". Make sure the ID token comes from the same Firebase project as the service account used to authenticate this SDK.
我尝试将
targetAudience
设置为 {projectId}
,但随后 auth.getIdTokenClient(targetAudience);
失败并出现 401 Unauthorized
响应。
被调用/调用的函数正在使用
functions.https.onCall
来验证请求。 如果我将其切换为 functions.https.onRequest
,它可以工作,但我不知道如何验证请求,我认为无论如何这是一个非常糟糕的解决方法,因为它应该与 onCall
方法一起使用。
对于
functions.https.onRequest
方法,它通过 Google Auth 签名的 JWT 授权标头,但 const decodedToken = await admin.auth().verifyIdToken(req.headers.authorization ?? '');
(source) 失败并出现错误:
Error: Decoding Firebase ID token failed. Make sure you passed the entire string JWT which represents an ID token.
我需要使用 Google Auth's
OAuth2Client.verifyIdToken
。 这没有很好的记录。 我必须在示例代码文件中找到解决方案;即使如此,也不清楚如何验证令牌的有效负载(他们的示例验证方法似乎相当弱)。 这是我处理完整请求的示例:
import { OAuth2Client } from 'google-auth-library';
export const exampleFunction = functions.https.onRequest(async (req, res) => {
// Note that I couldn't find a way to get the function name from the request object. :(
const functionName = 'exampleFunction';
// Note that you may have a different service account email if your Cloud Function
// is managed by a different account than the default.
const expectedServiceAccountEmail = `[email protected]`;
const parts = req.headers.authorization?.split(' ');
if (!parts || parts.length !== 2 || parts[0] !== 'Bearer' || !parts[1]) {
console.error('Bad header format: Authorization header not formated as \'Bearer [token]\'', req.headers);
throw new functions.https.HttpsError('unauthenticated', 'user not authenticated');
}
try {
const audience = `${req.protocol}://${req.hostname}/${functionName}`;
const googleOAuth2Client = new OAuth2Client();
const decodedToken = await googleOAuth2Client.verifyIdToken({
idToken: parts[1],
audience,
});
const payload = decodedToken.getPayload();
if (!payload) {
console.error('unpexpected state; missing payload', decodedToken);
throw new Error('no payload');
}
if (payload.aud !== audience) {
console.error('bad audience', payload);
throw new functions.https.HttpsError('permission-denied', 'bad audience');
}
if (payload.iss !== 'https://accounts.google.com') {
console.error('bad issuer', payload);
throw new functions.https.HttpsError('permission-denied', 'bad issuer');
}
if (payload.exp < Date.now() / 1000) {
console.error('expired token', payload);
throw new functions.https.HttpsError('permission-denied', 'expired token');
}
if (!payload.email_verified) {
console.error('email not verified', payload);
throw new functions.https.HttpsError('permission-denied', 'email not verified');
}
if (payload.email !== expectedServiceAccountEmail) {
console.error('invalid email', payload);
throw new functions.https.HttpsError('permission-denied', 'invalid email');
}
} catch (e) {
console.error(e);
throw new functions.https.HttpsError('permission-denied', 'bad authorization id token');
}
res.status(200).send('ok');
});
这才是正确的做法:
在 React Native 前端 -
// Starting File
useEffect(() => {
GoogleSignin.configure({
webClientId:
"Your Key Here",
});
}, []);
// Other file
import { GoogleSignin } from "@react-native-google-signin/google-signin";
const signIn = async () => {
try {
await GoogleSignin.hasPlayServices();
await GoogleSignin.signOut();
const response = await GoogleSignin.signIn();
if (isSuccessResponse(response)) {
const idToken = response.data.idToken;
// Sending idToken to backend
const res = await apiClient.post(GOOGLE_LOGIN_ROUTE, { idToken });
} else {
// sign in was cancelled by user
}
} catch (error) {
console.log(error);
}
};
在 Express.js/Node.js 后端 - 使用“google-auth-library”
import { OAuth2Client } from "google-auth-library";
const client = new OAuth2Client(
"Same webClientId used in the frontend"
);
export const googleLogin = async (request, response) => {
const { idToken } = request.body;
const ticket = await client.verifyIdToken({
idToken,
audience:
"Same webClientId used in the frontend put here again ",
});
const googleUser = ticket.getPayload();
const { email, name } = googleUser;
}
在 Firebase 在
google-services.json
文件中提供的所有 3 个位置中使用相同的 webClientId