如何持久化用户登录?Supabase认证状态保持问题求助
问题:跳过登录界面后Supabase无法识别用户身份
我设置了两个导航器(已登录/未登录),可以通过代码跳过登录界面,但此时Supabase无法识别用户,导致无法读写受保护的表。直接通过登录表单登录时一切正常,但通过本地存储的token跳过登录后就失效了。试过Auth Context Provider、getSession()/getUser()等方法都没用。
现有代码片段
导航器 index.js
const AppNavigator = () => { const dispatch = useDispatch(); const loggedIn = useSelector((state) => state.auth.loggedIn); useEffect(() => { const fetchAuthInfo = async () => { try { const token = await getFromAsyncStorage("AUTH_TOKEN"); if (!token) { return dispatch(authActions.updateLoggedInState(false)); } else { dispatch(authActions.updateLoggedInState(true)); } } catch (error) { Toast.show("Oops, something went wrong!"); } }; fetchAuthInfo(); }, []); return ( <NavigationContainer> <Root.Navigator> {loggedIn ? ( <Root.Screen name="MainNavigator" component={MainNavigator} options={{ headerShown: false }} /> ) : ( <Root.Screen name="AuthNavigator" component={AuthNavigator} options={{ headerShown: false }} /> )} <Root.Screen name="ForgotPassword" component={ForgotPassword} options={{ headerShown: false }} /> </Root.Navigator> </NavigationContainer> ); };
登录页面核心逻辑
const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const loggedIn = useSelector((state) => state.auth.loggedIn); const dispatch = useDispatch(); const handleLogin = async () => { let { data, error } = await supabase.auth.signInWithPassword({ email, password, }); if (error) { Toast.show(error.message); console.log(error); } if (data.session) { await saveToAsyncStorage("AUTH_TOKEN", data.session.access_token); dispatch(authActions.updateLoggedInState(true)); console.log("logged in", data.session.user.id); } };
问题根源
你现在的逻辑只是自己在Redux里标记了用户已登录,但Supabase客户端完全不知道这个本地存储的token——所有Supabase请求都需要携带有效的会话信息才能通过RLS策略,而你没有把存储的token同步到Supabase客户端中。
另外,只存储access_token是不够的:Supabase会话需要access_token和refresh_token(用来自动刷新过期的访问令牌),单独的access_token无法维持完整会话。
解决方案
1. 修改登录逻辑:存储完整会话对象
登录成功后,不要只存access_token,把整个session对象序列化后存入本地存储:
if (data.session) { // 存储完整会话,而非单独的access_token await saveToAsyncStorage("AUTH_SESSION", JSON.stringify(data.session)); dispatch(authActions.updateLoggedInState(true)); console.log("logged in", data.session.user.id); }
2. 修改导航器逻辑:恢复Supabase会话
在启动时取出存储的会话,调用Supabase的setSession方法恢复认证状态,再更新Redux:
useEffect(() => { const fetchAuthInfo = async () => { try { const sessionString = await getFromAsyncStorage("AUTH_SESSION"); if (!sessionString) { return dispatch(authActions.updateLoggedInState(false)); } const session = JSON.parse(sessionString); // 通知Supabase恢复会话 const { error } = await supabase.auth.setSession({ access_token: session.access_token, refresh_token: session.refresh_token }); if (error) { // 会话无效(比如令牌过期),清除存储并强制登录 await removeFromAsyncStorage("AUTH_SESSION"); dispatch(authActions.updateLoggedInState(false)); Toast.show("会话已过期,请重新登录"); return; } // 会话有效,更新登录状态 dispatch(authActions.updateLoggedInState(true)); } catch (error) { Toast.show("Oops, something went wrong!"); dispatch(authActions.updateLoggedInState(false)); } }; fetchAuthInfo(); }, []);
3. 可选:添加状态监听增强可靠性
在App根组件中添加Supabase auth状态监听,自动同步Redux和本地存储,避免状态不一致:
useEffect(() => { // 监听Supabase认证状态变化 const subscription = supabase.auth.onAuthStateChange((event, session) => { if (session) { // 登录/会话刷新时,保存会话到本地并更新Redux saveToAsyncStorage("AUTH_SESSION", JSON.stringify(session)); dispatch(authActions.updateLoggedInState(true)); } else { // 登出/会话过期时,清除存储并更新Redux removeFromAsyncStorage("AUTH_SESSION"); dispatch(authActions.updateLoggedInState(false)); } }); // 组件卸载时取消监听 return () => subscription.unsubscribe(); }, [dispatch]);
验证逻辑
完成修改后:
- 重启App,会自动从本地存储恢复Supabase会话,此时调用
supabase.auth.getUser()应该能返回当前用户 - 所有访问受保护表的请求会自动带上认证信息,RLS策略可以正常识别用户ID
内容的提问来源于stack exchange,提问作者Josie Jones
相关产品推荐
相关产品推荐

