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

使用Firebase谷歌登录时出现invalid-credential错误的原因排查

Firebase谷歌登录二次登录失败:invalid-credential 错误分析与修复

问题现象

首次通过谷歌账号登录Firebase可正常完成,但退出账号后再次尝试登录时失败,触发以下错误:

Error signing in: [firebase_auth/invalid-credential] The supplied auth credential is incorrect, malformed or has expired. [ ID Token issued at 1733745109 is stale to sign-in.

用户实现代码

class AuthService {
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
  final GoogleSignIn _googleSignIn = GoogleSignIn(); // GoogleSignIn instance

  // Sign in method with Google Sign-In and Firebase Authentication
  Future<User?> signIn() async {
    try {
      // Step 1: Trigger the Google Sign-In process
      final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();

      if (googleUser == null) {
        // If the user cancels the sign-in process
        print("Google Sign-In canceled.");
        return null;
      }

      // Step 2: Retrieve authentication details from Google
      final GoogleSignInAuthentication googleAuth = await googleUser.authentication;

      // Step 3: Create Firebase credential from Google authentication details
      final AuthCredential credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );

      // Step 4: Sign in to Firebase with the Google credentials
      final UserCredential userCredential = await _firebaseAuth.signInWithCredential(credential);
      return userCredential.user;
    } catch (e) {
      print("Error signing in with Google: $e");
      return null;
    }
  }

  // Get the current user from Firebase
  Future<User?> getCurrentUser() async {
    try {
      User? user = _firebaseAuth.currentUser;
      if (user != null) {
        await user.getIdToken(true); // Force token refresh if needed
      }
      return user;
    } catch (e) {
      print("Error getting current user: $e");
      return null;
    }
  }

  // Check if the user is signed in with Firebase
  bool isSignedIn() {
    return _firebaseAuth.currentUser != null;
  }

  // Manually refresh the token before performing authenticated operations
  Future<String?> getFreshToken() async {
    try {
      User? user = _firebaseAuth.currentUser;
      if (user != null) {
        String? token = await user.getIdToken(true); // Forces refresh
        return token;
      } else {
        print('No user is signed in');
        return null;
      }
    } catch (e) {
      print('Error refreshing token: $e');
      return null;
    }
  }
}

错误原因分析

  • GoogleSignIn本地会话未清除:当前代码未实现完整的登出逻辑,退出时仅清除了Firebase的用户会话,但GoogleSignIn实例仍在本地保留着之前的用户授权缓存。再次调用signIn()时,GoogleSignIn会直接返回缓存的旧会话信息,其中的idToken已经过期,导致Firebase验证失败。
  • 首次登录正常的原因:首次登录时GoogleSignIn无本地缓存,会触发完整的账号选择、授权流程,获取的是新鲜有效的token,因此可以正常通过Firebase验证。

修复方案

1. 实现完整的登出方法

在AuthService中添加同时退出GoogleSignIn和Firebase的方法,确保本地缓存被彻底清除:

Future<void> signOut() async {
  await _googleSignIn.signOut(); // 先清除Google本地会话
  await _firebaseAuth.signOut(); // 再清除Firebase会话
}

注意:必须先调用_googleSignIn.signOut(),再调用Firebase的signOut,避免GoogleSignIn残留旧会话。

2. 强制每次登录获取新token(可选)

如果需要用户每次登录都重新选择账号或获取新的授权,可以在signIn()方法开头先检查并清除GoogleSignIn的会话:

Future<User?> signIn() async {
  try {
    // 登录前先清除GoogleSignIn的旧会话
    if (await _googleSignIn.isSignedIn()) {
      await _googleSignIn.signOut();
    }

    final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
    // 后续原有逻辑...
  } catch (e) {
    print("Error signing in with Google: $e");
    return null;
  }
}

3. 辅助排查:验证token有效期

可以在获取Google认证信息后,解码idToken查看过期时间,确认是否为过期问题:

final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
// 解码idToken的payload部分(需要dart:convert库)
import 'dart:convert';
import 'dart:typed_data';

String? decodeIdToken(String token) {
  final parts = token.split('.');
  if (parts.length != 3) return null;
  final payload = parts[1];
  final normalized = base64Url.normalize(payload);
  final bytes = base64Url.decode(normalized);
  return utf8.decode(bytes);
}

// 使用示例
if (googleAuth.idToken != null) {
  print("Token Payload: ${decodeIdToken(googleAuth.idToken!)}");
}

解码后可查看exp字段(过期时间戳),确认是否早于当前时间。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:20:17