我正在使用
expo-auth-session
库在我的 React Native 项目中实现 Google 身份验证。
问题是,在选择用户并接受同意屏幕后,当redirectBack恰好返回Google响应令牌时,管理身份验证的AuthContext似乎配置错误。我相信重定向会导致视图重新加载,并且响应未正确处理。
为什么我认为问题出在 AuthContext 上? 因为如果我将所有登录逻辑移至主 _layout.tsx 文件中并删除 AuthContext,它就会正常工作并正确返回令牌。
奇怪的是登录工作正常...(使用我的后端),我认为这可能与谷歌的重定向有关
这是我的应用程序目录结构:
好吧,这就是
app/_layout.tsx
(入口点)
function RootLayoutNavigation() {
const tamaguiConfig = createTamagui(config);
return (
<TamaguiProvider config={tamaguiConfig}>
<Theme name="light">
<AuthProvider>
<SafeAreaView style={{ flex: 1 }}>
<StatusBar barStyle="default" backgroundColor="#f2f2f2" translucent />
<Slot />
</SafeAreaView>
</AuthProvider>
</Theme>
</TamaguiProvider>
);
}
为了管理身份验证,我创建了一个上下文(我怀疑它配置错误,可能是问题的根源)
AuthContext.tsx
import React, {useContext, createContext, type PropsWithChildren, useEffect, useState} from 'react';
import {loadUser, login, logout} from "~/services/AuthService";
import {useRouter, useSegments} from "expo-router";
import {getToken} from "~/services/TokenService";
import {ActivityIndicator} from "react-native";
import {View} from "tamagui";
type User = any;
interface AuthContextType {
user: User | null;
signIn: (userData: User) => void;
signOut: () => Promise<void>;
isLoading: boolean;
isAuthenticated: boolean;
}
const AuthContext = createContext<AuthContextType>({
user: null,
signIn: () => {},
signOut: async () => {},
isLoading: true,
isAuthenticated: false,
});
function useProtectedRoute(user: User | null) {
const segments = useSegments();
const router = useRouter();
console.log("segments", segments)
useEffect(() => {
if (segments.length === 0) return;
const inAuthGroup = segments[0] === '(auth)';
if (!user && !inAuthGroup) {
console.log("entra 1")
router.replace('/(auth)/login');
} else if (user && inAuthGroup) {
console.log("entra 2")
router.replace('/(drawer)');
}
}, [user, segments]);
}
export function AuthProvider({ children }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useProtectedRoute(user);
useEffect(() => {
async function loadSession() {
try {
const token = await getToken();
if (token) {
const userData = await loadUser();
setUser(userData);
setIsAuthenticated(true);
}
} catch (error) {
console.error('Error loading session:', error);
} finally {
setIsLoading(false);
}
}
loadSession();
}, []);
const signIn = (userData: User) => {
setUser(userData);
setIsAuthenticated(true);
};
const signOut = async () => {
try {
await logout();
setUser(null);
setIsAuthenticated(false);
} catch (error) {
console.error('Error during logout:', error);
}
};
return (
<AuthContext.Provider value={{ user, signIn, signOut, isLoading, isAuthenticated }}>
{!isLoading ? children : (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" />
</View>
)}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
还有登录页面
login.tsx
const Login = () => {
const { signIn } = useAuth();
const router = useRouter()
const [isLoadingLogin, setIsLoadingLogin] = useState(false)
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [errors, setErrors] = useState({})
const config = {
androidClientId: "client-id",
webClientId: "client-id 2",
expoClientId: "client-id 3"
}
const [request, response, promptAsync] = Google.useAuthRequest(config);
React.useEffect(() => {
handleEffect()
}, [response])
async function handleEffect(){
// Here is always null.
console.warn("handle Effect() - token " + response)
}
return (
<SafeAreaView style={{flex: 1, backgroundColor: "#e3e3e3"}}>
<H4>Log-in</H4>
<Text>{JSON.stringify(response)}</Text> // Also there is always null.
<View minWidth={350} marginVertical={12} flex={1} gap={10}>
<View marginBottom={10}>
<Input
placeholder="Email"
value={email}
onChangeText={(text) => {
setEmail(text);
}}
keyboardType="email-address"
autoCapitalize="none"
/>
</View>
<View>
<Input
placeholder="Contraseña"
value={password}
onChangeText={setPassword}
secureTextEntry={true}
autoCapitalize="none"
/>
</View>
<View>
<Pressable
onPress={() => promptAsync()}
style={({ pressed }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 10,
borderRadius: 30,
backgroundColor: pressed ? '#f0f0f0' : '#ffffff',
borderWidth: 1,
borderColor: '#dcdcdc',
})}
>
<Text paddingRight={9} fontWeight={600} fontSize={15.5}>
Entrar con Google
</Text>
<Image
source={require('~/assets/images/google-logo.png')}
style={{
width: 22,
height: 22,
}}
/>
</Pressable>
</View>
</View>
</SafeAreaView>
)
}
问题出现是因为我在Google.useAuthRequest中没有redirectUri参数。
const config = {
androidClientId: "client-id",
webClientId: "client-id 2",
expoClientId: "client-id 3",
redirectUri: "schema:///login" - Need this line of code.
这样,一旦重定向到您的应用程序,令牌就会返回到视图。
就我而言,尽管一切都配置良好,但发生的情况是,在重定向回来时,由于我配置 AuthContext 的方式,令牌没有返回到登录并且没有捕获它。
令人好奇的是,如果我直接在应用程序的入口点实现此逻辑
app/_layout.tsx
它可以正常工作,但一定是通过我创建的 AuthContext 的某些移动,有必要很好地管理重定向问题。
Expo Auth 会话库文档: https://docs.expo.dev/versions/latest/sdk/auth-session/