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

Expo React Native项目PHP端无法验证Firebase ID Token求助

Expo Firebase登录ID Token后端PHP验证失败问题

我有一个基于Expo的项目,使用Firebase的createUserWithEmailAndPassword和signInWithEmailAndPassword实现客户端登录,功能正常。但在PHP后端用JWT PHP库和Kreait Firebase Admin SDK验证ID Token时,分别出现「Signature verification failed」和「invalid token」错误。

环境配置

  • 使用npm expo install firebase安装Firebase依赖
  • 通过npm expo run:ios --device在iOS设备上运行项目
  • 后端采用PHP开发

React Native相关代码片段

import { initializeApp } from 'firebase/app';
import { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword, updateProfile } from 'firebase/auth';
const firebaseConfig = {
   apiKey: "AIzaxxxxxxxxxxxxxxxxxxJxk",
   authDomain: "oxxxxxxxxx.firebaseapp.com",
   projectId: "oxxx-xxxx",
   storageBucket: "oxxx-xxxx.firebasestorage.app",
   messagingSenderId: "1111111111",
   appId: "1:111111111:web:1111111111",
   measurementId: "G-5555555555"
};

export default function Index() {
  const app = initializeApp(firebaseConfig);
   const auth = getAuth(app);

// Email/Password Authentication
   const handleEmailAuth = async () => {
       if (!email || !password) {
           Alert.alert('Error', 'Please fill in all fields');
           return;
       }

       if (!isLogin && password !== confirmPassword) {
           Alert.alert('Error', 'Passwords do not match');
           return;
       }
       if (!isLogin && !name.trim()) {
           Alert.alert('Error', 'Please enter your name');
           return;
       }

       setLoading(true);
       try {
           let userCredential;
           
           if (isLogin) {
               userCredential = await signInWithEmailAndPassword(auth, email, password);
           } else {
               userCredential = await createUserWithEmailAndPassword(auth, email, password);
           }
           // Update user profile with name
           
           await updateProfile(userCredential.user, {
               displayName: name.trim()
           }).then(() => {
               userCredential.user.reload().then(() => { 
                   console.log('Profile reloaded - displayname: ' + JSON.stringify(userCredential.user));
               });
           }).catch((error) => {
               console.log(error);
           }); 

           await registerUserInBackend(auth.currentUser, "emailpwd");
           
       } catch (error: any) {
           Alert.alert('Error', error.message);
       } finally {
           setLoading(false);
       }
   };

const registerUserInBackend = async (responsedata: any, provider: string) => {
       try {
           const user = (provider == "google" ? responsedata.user : responsedata);
           const firebaseIdToken = provider!="google" ? JSON.stringify(await user.getIdToken(true)): null;
           
           const response = await fetch('https://qqqqqqqq.com/register_user.php', {
               method: 'POST',
               headers: {
                   'Content-Type': 'application/json',
               },
               body: JSON.stringify({
                   idToken: provider == "google" ? responsedata.idToken : firebaseIdToken,
                   email: user.email,
                   name: provider=="google" ? user.name : user.displayName,
                   photo_url: provider == "google" ? user.photo: "",
                   provider: provider
               })
           });

           const data = await response.json();
           if (data.success) {
               storeUserSession(user, data.uid, responsedata.idToken);
               await signIn({
                   uid: data.uid,
                   accessToken: responsedata.accessToken,
                   email: user.email,
                   displayName: user.name,
                   photoURL: user.photo
               });
               router.replace('/(tabs)/mainScreen');
               setLoading(false);
           } 
           else {
               setLoading(false);
               throw new Error(data.error);
           }
       } catch (error) {
           console.error('Backend registration error:', error);
       }
   };

PHP相关代码片段

require_once '../../vendor/autoload.php';
use Google\Client as GoogleClient;
use Kreait\Firebase\Factory;
use Kreait\Firebase\Auth;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use GuzzleHttp\Client;

public function verifyFirebaseIdToken($idToken) {
        try {
            error_log('inside verifyFirebaseIdToken'.$idToken);
            // Verify the ID token
            $verifiedIdToken = $this->auth->verifyIdToken($idToken);
            
            // Get user info
            return [
                'success' => true,
                'uid' => $verifiedIdToken->claims()->get('sub'),
                'email' => $verifiedIdToken->claims()->get('email'),
                'email_verified' => $verifiedIdToken->claims()->get('email_verified'),
                'name' => $verifiedIdToken->claims()->get('name')
            ];
        } catch (\Kreait\Firebase\Exception\Auth\FailedToVerifyToken $e) {
            error_log('inside catch1'.$e);
            return ['success' => false, 'error' => 'Invalid token'];
        } catch (\Exception $e) {
            error_log('inside catch2'.$e);
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }

public function verifyIdToken($idToken) {
        try {
            $tokenParts = explode('.', $idToken);
            if (count($tokenParts) != 3) {
                throw new Exception('Invalid token format');
            }

            // Get token header to determine which key to use
            $header = json_decode(base64_decode($tokenParts[0]), true);
            if (!isset($header['kid']) || !isset($this->publicKeys[$header['kid']])) {
                throw new Exception('Invalid token header');
            }

            $publicKey = $this->publicKeys[$header['kid']];
            
            error_log('verifyIdToken - idtoken:'.$idToken);
            error_log('verifyIdToken - kid:'.$header['kid']);
            error_log('verifyIdToken - publicKey:'.$publicKey);
            $decodedToken = JWT::decode($idToken, new Key($publicKey, 'RS256'));
            error_log('verifyIdToken - decodedToken:'.$decodedToken);
            
            // Verify issuer and audience
            if ($decodedToken->iss !== 'https://securetoken.google.com/oxxx-00000') {
                throw new Exception('Invalid token issuer');
            }

            return [
                'success' => true,
                'uid' => $decodedToken->sub,
                'email' => $decodedToken->email ?? null,
                'email_verified' => $decodedToken->email_verified ?? false,
                'name' => $decodedToken->name ?? null
            ];

        } catch (Exception $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }

// in register_user.php
        error_log('token: '.$data['idToken']);
        error_log('email: '.$data['email']);
        
        if($data['provider']=='google')
            $verifyTokenResponse = $Oxxx->validateGoogleToken($data['idToken']);
        else
            $verifyTokenResponse = $Oxxx->verifyFirebaseIdToken($data['idToken']);
        error_log('verifyTokenResponse: '.implode($verifyTokenResponse));

已完成的配置

  • Firebase控制台已配置iOS、Android和Web平台
  • 已添加GoogleServices.plist到项目根目录
  • 曾尝试使用react-native-firebase/auth及/app模块,仍出现相同错误;最终改用npm install firebase
  • 已尝试删除node_modules并重新安装(npm install + pod install),问题依旧

已验证的内容

  • 客户端与服务端打印的idToken完全一致
  • idToken中的kid可在Google公开密钥列表中找到
  • Firebase控制台「用户」标签显示用户已成功注册
  • 在jwt.io中可正常解码idToken,能看到正确的头部和负载信息,但始终显示「invalid signature」
  • 在handleEmailAuth()中尝试传递userCredential.user替代auth.currentUser,错误仍存在

请问我遗漏了什么配置或步骤?希望有经验的开发者能提供帮助。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:08:09