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

如何使用Expo AuthSession正确处理Access与Refresh Token?

处理OAuth令牌过期与Refresh Token使用

1. 先确保能获取到Refresh Token

首先,你的授权请求需要包含offline_access scope(大部分OAuth 2.0服务要求这个才能返回refresh token),同时在exchangeCodeAsync成功后,要把返回的refresh_token和过期时间一起存入AsyncStorage:

export const Step1 = () => {
  const [instanceUrl, setInstanceUrl] = useState('')
  const [request, response, promptAsync] = AuthSession.useAuthRequest(
    {
      clientId: '...',
      clientSecret: '...',
      redirectUri: 'exp://localhost:19000',
      // 添加offline_access scope以获取refresh token
      scopes: ['read', 'write', 'follow', 'offline_access'],
    },
    discovery
  );

  const handleLoginPress = () => {
    promptAsync()
  }

  useEffect(() => {
    const fetchAccessToken = async () => {
      const tokenResult = await AuthSession.exchangeCodeAsync({
        clientId: '...',
        clientSecret: '...',
        code: response.params.code,
        redirectUri: 'exp://localhost:19000',
      }, discovery)

      // 计算过期时间:当前时间 + expires_in秒(转为毫秒)
      const expiresAt = Date.now() + tokenResult.expires_in * 1000;
      // 把accessToken、refreshToken、过期时间都存起来
      await AsyncStorage.multiSet([
        ['accessToken', tokenResult.access_token],
        ['refreshToken', tokenResult.refresh_token],
        ['expiresAt', expiresAt.toString()]
      ])
      
      console.log({ tokenResult })
    }
    if (response.type === 'success') {
      fetchAccessToken()
    }
  }, [response])

  // ... 原有返回部分
}

2. 实现令牌过期检查与刷新逻辑

你需要写两个核心函数:检查令牌是否过期,以及用refresh token获取新的access token。建议把这些逻辑抽成可复用的工具函数,方便跨组件调用。

检查令牌是否过期

const isTokenExpired = async () => {
  const expiresAtStr = await AsyncStorage.getItem('expiresAt');
  if (!expiresAtStr) return true;
  const expiresAt = parseInt(expiresAtStr, 10);
  // 当前时间超过过期时间则返回true(令牌已过期)
  return Date.now() > expiresAt;
}

刷新AccessToken的函数

const refreshAccessToken = async (discovery) => {
  const refreshToken = await AsyncStorage.getItem('refreshToken');
  if (!refreshToken) throw new Error('No refresh token available');

  try {
    const newTokenResult = await AuthSession.exchangeCodeAsync({
      clientId: '...',
      clientSecret: '...',
      grantType: 'refresh_token', // 指定授权类型为refresh_token
      refreshToken: refreshToken,
      redirectUri: 'exp://localhost:19000', // 部分服务要求与授权时一致
      scopes: ['read', 'write', 'follow'], // 保持原有权限范围
    }, discovery);

    // 更新存储的令牌和过期时间
    const expiresAt = Date.now() + newTokenResult.expires_in * 1000;
    await AsyncStorage.multiSet([
      ['accessToken', newTokenResult.access_token],
      ['refreshToken', newTokenResult.refresh_token || refreshToken], // 若返回新的refreshToken则更新,否则沿用旧的
      ['expiresAt', expiresAt.toString()]
    ]);

    return newTokenResult.access_token;
  } catch (error) {
    // 刷新失败时清空存储,引导用户重新登录
    console.error('Failed to refresh token:', error);
    await AsyncStorage.removeItem('accessToken');
    await AsyncStorage.removeItem('refreshToken');
    await AsyncStorage.removeItem('expiresAt');
    throw error;
  }
}

3. 代码存放位置与使用时机

  • 工具函数存放:如果多个组件需要用到认证逻辑,建议把isTokenExpired和refreshAccessToken抽成单独的文件(比如utils/auth.js),避免代码冗余。
  • 使用时机:在每次发起需要授权的API请求前,先检查令牌状态:
const fetchProtectedData = async (discovery) => {
  const expired = await isTokenExpired();
  if (expired) {
    await refreshAccessToken(discovery);
  }
  const accessToken = await AsyncStorage.getItem('accessToken');
  
  // 用新的accessToken发起请求
  const response = await fetch(`${instanceUrl}/api/protected`, {
    headers: {
      Authorization: `Bearer ${accessToken}`
    }
  });
  return response.json();
}

4. 异常处理补充

如果refresh token也过期或失效,refreshAccessToken会抛出错误,此时需要在业务代码中捕获错误,比如清空用户登录状态并跳转到登录界面,引导用户重新授权。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 00:41:01