You need to enable JavaScript to run this app. 前端基于JS与Axios实现每次API请求携带Firebase认证令牌的问题排查
优惠活动
大模型
产品
解决方案
定价
更多

前端基于JS与Axios实现每次API请求携带Firebase认证令牌的问题排查

前端端侧问题解决方案(解决令牌异步抢跑、返回Promise的核心问题)

核心问题根因定位

之前遇到的两类异常本质上是同一个原因:

  • Axios默认会按代码顺序执行请求动作,异步获取令牌的流程没有被纳入请求的前置阻塞流程,导致请求先于令牌获取完成发出
  • 调用异步的getIdToken方法时,没有在async作用域内用await接收返回值,直接把Promise对象当成令牌字符串塞到请求头里

第一步:封装带缓存逻辑的统一令牌获取方法

不要把令牌获取逻辑散落在各个交互事件里,在token.js里封装全局可复用的异步方法,自带本地缓存减少Firebase接口调用次数:

// 全局缓存变量,避免重复拉取令牌
let cachedIdToken = null;
let tokenValidUntil = 0;

export async function fetchValidIdToken() {
  // 优先使用本地未过期的令牌,提前5分钟触发刷新,避免请求过程中令牌刚好失效
  const now = Date.now();
  if (cachedIdToken && now < tokenValidUntil - 5 * 60 * 1000) {
    return cachedIdToken;
  }

  const currentFirebaseUser = firebase.auth().currentUser;
  // 无登录态时直接抛出异常,不返回空值
  if (!currentFirebaseUser) {
    throw new Error("未检测到有效登录态");
  }

  // 强制拉取最新有效令牌,不使用本地可能过期的缓存
  const freshToken = await currentFirebaseUser.getIdToken(true);
  // 解析令牌中的过期时间戳,更新本地缓存
  const tokenPayload = JSON.parse(atob(freshToken.split(".")[1]));
  cachedIdToken = freshToken;
  tokenValidUntil = tokenPayload.exp * 1000;

  return freshToken;
}

第二步:配置Axios全局请求拦截器,从流程上杜绝令牌抢跑

所有后端请求都通过统一配置的Axios实例发出,在拦截器里完成令牌获取、请求头拼接的全流程,拦截器内部的异步流程会阻塞请求的实际发送动作,直到令牌拿到再继续执行,从根源上解决请求先跑、令牌为空的问题:

// 全局Axios实例初始化
import axios from "axios";
import { fetchValidIdToken } from "./token.js";

const apiClient = axios.create({
  baseURL: "/api", // 匹配Django后端的路由前缀
  timeout: 10000
});

// 请求拦截器:所有发出的请求都会先经过这里的逻辑处理
apiClient.interceptors.request.use(async (requestConfig) => {
  try {
    // 等待令牌获取完成,拿到真实的字符串值后再拼接请求头
    const idToken = await fetchValidIdToken();
    // 按照Django后端的鉴权要求,设置Authorization请求头,格式为Bearer 令牌值
    requestConfig.headers.Authorization = `Bearer ${idToken}`;
  } catch (err) {
    // 令牌获取失败时直接中断请求,跳转登录页
    console.error("身份令牌获取失败", err);
    window.location.href = "/login";
    return Promise.reject(err);
  }
  return requestConfig;
}, (error) => {
  return Promise.reject(error);
});

// 导出配置完成的Axios实例,业务代码直接引用使用
export default apiClient;

业务代码的正确调用方式

对比之前容易踩坑的写法,业务代码里不需要手动处理令牌逻辑,直接调用封装好的Axios实例即可:

  • 错误写法(之前踩过的坑)
    // 按钮点击事件里的错误实现:直接把异步函数返回的Promise当令牌使用
    document.querySelector("#submit-btn").addEventListener("click", () => {
      const tokenPromise = fetchValidIdToken();
      axios.post("/business/submit", { formData: "test" }, {
        headers: { Authorization: `Bearer ${tokenPromise}` } // 此处传入的是Promise对象,不是真实令牌
      })
    })
    
  • 正确写法
    // 按钮点击事件直接调用封装好的apiClient,无需手动处理令牌
    document.querySelector("#submit-btn").addEventListener("click", async () => {
      try {
        const response = await apiClient.post("/business/submit", { formData: "test" });
        // 处理业务返回结果
        console.log("请求成功", response.data);
      } catch (err) {
        console.error("请求失败", err);
      }
    })
    

Django后端的兼容配置

因为前端请求头传的是Bearer {令牌字符串}的格式,后端提取令牌时需要去掉前缀再做校验,避免verify_id_token解码失败:

# Django鉴权逻辑示例
from firebase_admin import auth
from rest_framework import authentication

class FirebaseAuthMiddleware(authentication.BaseAuthentication):
    def authenticate(self, request):
        auth_header = request.META.get("HTTP_AUTHORIZATION")
        if not auth_header:
            return None
        # 拆分出Bearer后面的真实令牌字符串
        id_token = auth_header.split("Bearer ")[1]
        try:
            # 调用firebase_admin的接口解码校验
            decoded_result = auth.verify_id_token(id_token)
            uid = decoded_result["uid"]
            # 返回认证通过的用户和对应标识
            return (request.user, uid)
        except Exception:
            raise authentication.errors.AuthenticationFailed("身份令牌校验不通过")
        return None

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.01 01:15:08