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

Firebase Token每小时过期如何解决?SvelteKit+Python架构场景

Firebase ID Token 每小时过期问题排查与解决

问题背景

前端基于SvelteKit开发,后端采用Python,通过Firebase Admin SDK完成服务端身份校验,并将Token存储为Cookie,每次请求后端时携带该Cookie。但实际运行中,Firebase Token每小时就会过期,报错信息如下:

{
  "detail": "Invalid authentication. Token expired, 1700799416 < 1700846629"
}

查阅文档得知Firebase会自动刷新Token,但实际未生效,以下是相关代码:

客户端获取Token代码

export async function googleAuth() {
    const provider = new GoogleAuthProvider();
    const credential = await signInWithPopup(auth, provider);

    const idToken = await credential.user.getIdToken();

    try {
        const res = await fetch('/api/signin', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ idToken })
        });

        if (!res.ok) {
            throw new Error('Network response was not ok ' + res.statusText);
        }

        console.log("User successfully signed in with Google");
        return credential;
    } catch (error) {
        console.error('There was a problem with the fetch operation:', error);
    }

    console.log("User successfully signed in with Google")
    return credential
}

服务端请求后端代码(SvelteKit PageServerLoad)

export const load = (async ({ locals, params }) => {

    const uid = locals.userID;
    const idToken = locals.idToken

    if (!uid) {
        throw redirect(301, "/login")
    }

    let message_bookmark: string = ''

    const formData = new FormData();
    formData.append("user_id", uid)
    formData.append("message_bookmark", message_bookmark)

    const messagesContent = await fetch(
        `${import.meta.env.VITE_API_URL}/get-message-history`,
        {
            method: "POST",
            body: formData,
            headers: {
                'Authorization': `Bearer ${idToken}`
            },
        }
    );

    const messagesContentData = await messagesContent.json()
  
    messagesContentData.body.idToken = idToken;
    messagesContentData.body.userId = uid;

    return messagesContentData

}) satisfies PageServerLoad

问题根源

  1. Token仅在登录时获取一次:当前仅在Google登录时获取ID Token并存入Cookie和locals,但Firebase ID Token默认有效期为1小时,后续请求一直使用旧Token,自然会过期。
  2. 前端自动刷新未同步到服务端:Firebase Auth SDK确实会在Token过期前自动刷新,但这个刷新仅发生在前端SDK内部,不会主动同步到服务端存储的Cookie或locals中。

解决方案

1. 前端监听Token刷新事件,同步新Token到服务端

利用Firebase的onIdTokenChanged事件,监听Token变化,每次刷新后将新Token发送到服务端更新Cookie:

import { onIdTokenChanged } from "firebase/auth";
import { auth } from "./path-to-your-firebase-config";

// 监听Token刷新,同步到服务端
onIdTokenChanged(auth, async (user) => {
  if (user) {
    const newIdToken = await user.getIdToken(true); // 强制获取最新Token
    try {
      await fetch('/api/refresh-token', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ idToken: newIdToken })
      });
    } catch (err) {
      console.error('更新Token失败:', err);
    }
  }
});

2. SvelteKit新增Token刷新API路由

创建/api/refresh-token/+server.ts,接收前端传来的新Token,验证后更新locals和Cookie:

import { adminAuth } from '$lib/firebase-admin';
import type { RequestHandler } from './$types';

export const POST: RequestHandler = async ({ request, cookies, locals }) => {
  const { idToken } = await request.json();
  
  try {
    // 验证新Token有效性
    const decodedToken = await adminAuth.verifyIdToken(idToken);
    // 更新locals
    locals.idToken = idToken;
    locals.userID = decodedToken.uid;
    
    // 更新Cookie,保持长期有效期(示例为7天)
    cookies.set('idToken', idToken, {
      path: '/',
      httpOnly: true,
      secure: import.meta.env.PROD,
      sameSite: 'strict',
      maxAge: 60 * 60 * 24 * 7 // 7天
    });
    
    return new Response(JSON.stringify({ success: true }), { status: 200 });
  } catch (err) {
    // Token验证失败,清除旧Cookie
    cookies.delete('idToken', { path: '/' });
    return new Response(JSON.stringify({ detail: 'Token验证失败' }), { status: 401 });
  }
};

3. 完善SvelteKit服务端Hook,确保每次请求读取最新Token

在hooks.server.ts中,每次请求从Cookie读取Token并验证,更新locals:

import { adminAuth } from '$lib/firebase-admin';
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
  const idToken = event.cookies.get('idToken');
  
  if (idToken) {
    try {
      const decodedToken = await adminAuth.verifyIdToken(idToken);
      event.locals.userID = decodedToken.uid;
      event.locals.idToken = idToken;
    } catch (err) {
      // Token无效,清除Cookie
      event.cookies.delete('idToken', { path: '/' });
    }
  }
  
  const response = await resolve(event);
  return response;
};

4. Python后端增加Token过期处理

当收到Token过期错误时,返回401状态码,前端可捕获后触发刷新流程并重试:

from firebase_admin import auth
from flask import request, jsonify

def verify_firebase_token(id_token):
    try:
        return auth.verify_id_token(id_token), None
    except auth.ExpiredIdTokenError:
        return None, "Token expired"
    except Exception as e:
        return None, str(e)

@app.route('/get-message-history', methods=['POST'])
def get_message_history():
    auth_header = request.headers.get('Authorization')
    if not auth_header or not auth_header.startswith('Bearer '):
        return jsonify({"detail": "Missing authentication token"}), 401
    
    id_token = auth_header.split(' ')[1]
    decoded_token, error = verify_firebase_token(id_token)
    
    if not decoded_token:
        if error == "Token expired":
            return jsonify({"detail": error}), 401
        return jsonify({"detail": error}), 401
    
    # 业务逻辑处理...
    return jsonify({"body": {"messages": []}})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 10:45:00