如何使用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
相关产品推荐
相关产品推荐

