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

如何在Node.js后端基于Passport.js实现refresh token自动刷新流程

实现方案

核心思路是封装统一的Google API请求实例,通过拦截器自动处理token刷新,完全不需要在每个业务路由里写重复逻辑。


第一步:先修复现有Passport策略的小缺陷

当前你写的策略里,老用户登录时没有更新token,后续刷新可能用到过期的refresh token,调整下逻辑:

// 原来的passport.use里的try块调整
try {
  let user = await User.findOne({ googleId: profile.id });
  if(user) {
    // 老用户登录也要更新最新的token和refresh token
    await Key.findOneAndUpdate(
      { googleId: user.googleId, keyProvider: 'Google' },
      { token: accessToken, refreshToken: refreshToken },
      { upsert: true }
    );
    done(null, user);
  } else {
    user = await User.create(newUser);
    await Key.create({
      googleId: user.googleId,
      keyProvider: 'Google',
      token: accessToken,
      refreshToken: refreshToken
    });
    done(null, user);
  }
} catch (err) {
  console.error(err);
  done(err);
}

第二步:编写通用刷新token工具函数

单独建个文件比如 utils/googleAuth.js,写刷新逻辑:

const axios = require('axios');
const Key = require('../models/Key'); // 导入你的Key模型

// 刷新access token
async function refreshGoogleToken(googleId) {
  // 从数据库查对应refresh token
  const keyRecord = await Key.findOne({ googleId, keyProvider: 'Google' });
  if (!keyRecord || !keyRecord.refreshToken) {
    throw new Error('未找到对应refresh token,请重新登录');
  }

  try {
    const res = await axios.post('https://oauth2.googleapis.com/token', {
      client_id: process.env.GOOGLE_CLIENT_ID,
      client_secret: process.env.GOOGLE_CLIENT_SECRET,
      refresh_token: keyRecord.refreshToken,
      grant_type: 'refresh_token'
    });

    const newAccessToken = res.data.access_token;
    // 更新数据库里的access token
    await Key.findOneAndUpdate(
      { googleId, keyProvider: 'Google' },
      { token: newAccessToken }
    );
    return newAccessToken;
  } catch (err) {
    // refresh token也失效了,需要用户重新授权
    await Key.findOneAndDelete({ googleId, keyProvider: 'Google' });
    throw new Error('refresh token失效,请重新登录');
  }
}

第三步:封装带自动刷新的axios实例

还是在 utils/googleAuth.js 里加这个实例,用响应拦截器处理401自动刷新重试:

// 用来避免多个请求同时触发多次刷新
let refreshingPromise = null;

const googleApiClient = axios.create({
  baseURL: 'https://www.googleapis.com',
  headers: {
    'Content-Type': 'application/json'
  }
});

// 响应拦截器
googleApiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    // 只有401错误、且不是重试请求的时候才走刷新逻辑
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      // 已经在刷新的话就等刷新完成
      if (!refreshingPromise) {
        // 从请求的自定义参数里拿googleId,后面发请求的时候要传
        refreshingPromise = refreshGoogleToken(originalRequest.googleId);
      }

      try {
        const newAccessToken = await refreshingPromise;
        // 更新请求的token
        originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
        return googleApiClient(originalRequest);
      } catch (err) {
        return Promise.reject(err);
      } finally {
        refreshingPromise = null;
      }
    }
    return Promise.reject(error);
  }
);

module.exports = { googleApiClient, refreshGoogleToken };

第四步:业务路由直接调用即可

原来的路由代码几乎不用改,直接用封装好的实例,传入当前用户的googleId就行:

const { googleApiClient } = require('../utils/googleAuth');

router.get('/list', async (req, res, next) => {
  console.log('list called');
  try {
    // 假设你用了passport的session,req.user里有googleId
    const userKey = await Key.findOne({ googleId: req.user.googleId, keyProvider: 'Google' });
    const result = await googleApiClient.get('/youtube/v3/subscriptions', {
      params: { channelId: '[someid]' },
      // 把当前用户的googleId传进去,供拦截器用
      googleId: req.user.googleId,
      headers: {
        // 第一次请求用数据库里存的token
        Authorization: `Bearer ${userKey.token}`
      }
    });
    console.log(result.data);
    res.json(result.data);
  } catch (err) {
    // 这里捕获的错误就是刷新失败的错误,直接返回给前端提示重新登录就行
    next(err);
  }
});

如果觉得每次查token麻烦,可以再封装个请求方法,自动根据传入的googleId查token加到头里,进一步简化业务代码。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 20:15:01