对 Firebase 函数到函数调用进行身份验证时,Firebase ID 令牌具有不正确的“aud”(受众)声明

问题描述 投票:0回答:2

我正在尝试让一个 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.
firebase google-cloud-platform firebase-authentication google-cloud-functions
2个回答
3
投票

我需要使用 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');
});

0
投票

这才是正确的做法:

在 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

© www.soinside.com 2019 - 2024. All rights reserved.