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
问题根源
- Token仅在登录时获取一次:当前仅在Google登录时获取ID Token并存入Cookie和
locals,但Firebase ID Token默认有效期为1小时,后续请求一直使用旧Token,自然会过期。 - 前端自动刷新未同步到服务端: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
相关产品推荐
相关产品推荐

