使用Firebase Auth时如何自动刷新过期idToken 无需浏览器全量刷新页面
解决方案
你可以通过Axios响应拦截器+Firebase内置的token刷新能力实现无感知刷新idToken,完全不需要页面刷新,具体实现步骤如下:
第一步:封装通用token获取方法
Firebase 提供的 getIdToken() 方法本身已经内置了过期判断逻辑,调用时会自动识别当前token是否过期,过期的话会自动向Firebase服务请求新token,不需要你手动处理刷新逻辑。先封装一个通用工具函数,方便后续在任意位置调用:
// 新建utils/auth.ts import firebase from '../config/firebase' import nookies from 'nookies' // 获取当前有效idToken export const getLatestIdToken = async () => { const currentUser = firebase.auth().currentUser if (!currentUser) return null return await currentUser.getIdToken() } // 强制刷新idToken export const forceRefreshIdToken = async () => { const currentUser = firebase.auth().currentUser if (!currentUser) return null const newToken = await currentUser.getIdToken(true) nookies.set(undefined, 'idk', newToken, { path: '/' }) return newToken }
然后把方法暴露到AuthContext中,方便组件内调用:
// AuthContext.tsx 改动部分 import { getLatestIdToken, forceRefreshIdToken } from '../utils/auth' type AuthContextType = { user: AuthUser | null login: (email: string, password: string) => Promise<any> signup: (email: string, password: string) => Promise<any> logout: () => Promise<any> oAuthLogin: (provider: string) => Promise<any> getLatestIdToken: () => Promise<string | null> forceRefreshIdToken: () => Promise<string | null> } // 在AuthContextProvider里添加方法实现 const returnObj = { user, login, signup, logout, oAuthLogin, getLatestIdToken, forceRefreshIdToken }
第二步:添加Axios响应拦截器,自动处理401场景
在你的axios配置文件里添加拦截器,当请求返回401状态码的时候,先尝试刷新token,然后用新的token重发原来的请求,用户完全感知不到这个过程:
// axios配置文件 import axios from 'axios' import { forceRefreshIdToken } from '../utils/auth' import nookies from 'nookies' let isRefreshing = false let failedRequestsQueue: Array<(token: string) => void> = [] axios.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config // 只处理鉴权失败且不是重发的请求 if (error.response?.status === 401 && !originalRequest._retry) { if (isRefreshing) { // 如果已经在刷新token,把当前请求加入队列等待刷新完成 return new Promise((resolve) => { failedRequestsQueue.push((newToken: string) => { originalRequest.headers.Authorization = `Bearer ${newToken}` resolve(axios(originalRequest)) }) }) } originalRequest._retry = true isRefreshing = true try { const newToken = await forceRefreshIdToken() if (!newToken) { // 拿不到新token说明登录状态已经失效,跳转到登录页 window.location.href = '/login' return Promise.reject(error) } // 更新全局默认请求头 axios.defaults.headers.common.Authorization = `Bearer ${newToken}` originalRequest.headers.Authorization = `Bearer ${newToken}` // 执行队列中所有待重发的请求 failedRequestsQueue.forEach((callback) => callback(newToken)) failedRequestsQueue = [] // 重发当前请求 return axios(originalRequest) } catch (err) { // 刷新失败,清空登录状态跳登录 await firebase.auth().signOut() nookies.destroy(undefined, 'idk') window.location.href = '/login' return Promise.reject(err) } finally { isRefreshing = false } } return Promise.reject(error) } )
可选优化:主动定时刷新token
如果你想完全避免出现401的情况,可以在AuthContext里加定时器,提前5分钟主动刷新token:
// AuthContextProvider 内新增useEffect useEffect(() => { if (!user) return // 每55分钟刷新一次,比1小时有效期早5分钟 const refreshTimer = setInterval(async () => { await forceRefreshIdToken() setUser(prev => prev ? {...prev, idToken: newToken} : null) }, 55 * 60 * 1000) return () => clearInterval(refreshTimer) }, [user])
注意
- 不需要修改你现有Express端的auth中间件,原有逻辑完全可以复用
- 拦截器里的队列逻辑是为了处理同时发起多个请求的场景,避免重复刷新多次token
内容的提问来源于stack exchange,提问作者sairaj
相关产品推荐
相关产品推荐

