Expo React Native动态切换OIDC认证端点问题求助
动态切换OIDC端点的Expo Auth Session实现方案
问题背景
我正在开发一个Expo React Native应用,需要根据用户输入的URI连接不同的OIDC端点。尝试在用户输入目标URI后,用AuthSession.makeRedirectUri生成新的redirectUri,再传入AuthSession.useAuthRequest,但遇到了React Hook调用规则的限制——不能在普通函数内部调用useAuthRequest、useAutoDiscovery这类Hook。
我的理想实现逻辑如下:
const connect = async (newURI) =>{ const redirectUri = AuthSession.makeRedirectUri({ useProxy:true, projectNameForProxy: `${newURI}`, path: '/idp/members' }); const discovery = AuthSession.useAutoDiscovery(`${newURI}`); const [request, result, promptAsync] = await AuthSession.useAuthRequest( { clientId: "ess-next", redirectUri, scopes:['openid', 'api1'], }, discovery ); promptAsync() }
核心问题分析
React Hook规则明确要求:所有Hook必须在组件的顶层作用域调用,不能嵌套在普通函数、条件判断或循环中。useAuthRequest和useAutoDiscovery都是Hook,因此无法像上述代码那样放在connect函数内执行。
解决方案
- 用组件状态管理当前要连接的URI,用户输入或选择历史URI时更新该状态
- 将
useAutoDiscovery和useAuthRequest放在组件顶层,依赖当前URI状态,URI变化时Hook自动重新执行,生成对应配置 - 在
connect函数中仅更新URI状态,调用生成好的promptAsync触发认证流程 - 监听认证结果状态,处理成功后的token存储等逻辑
修改后的完整代码
import { observer } from "mobx-react-lite" import React, { FC, useEffect, useState } from "react" import { translate } from "../i18n" import { MaterialCommunityIcons } from "@expo/vector-icons" import { Text } from "../components" import { Box, Input, Image, Flex, Button, FlatList, HStack, VStack, IconButton, Icon, Spacer, Pressable } from "native-base" import { StackScreenProps } from "@react-navigation/stack" import { AppStackScreenProps } from "../navigators" import { useStores } from "../models" import * as AuthSession from 'expo-auth-session' const appIcon = require("../../assets/images/icon.png") export const WelcomeScreen: FC<StackScreenProps<AppStackScreenProps<'Welcome'>>> = observer(function WelcomeScreen(_props) { const { uriStore, authStore } = useStores() const { history, lastConnectedUri } = uriStore const [uriInput, setUriInput] = useState("") // 新增状态:管理当前要连接的OIDC端点URI const [currentUri, setCurrentUri] = useState(lastConnectedUri || "") // 动态生成redirectUri,依赖currentUri const redirectUri = AuthSession.makeRedirectUri({ useProxy: true, projectNameForProxy: `${currentUri}`, path: '/idp/members' }) // 动态获取discovery文档,依赖currentUri(空值时返回null避免报错) const discovery = currentUri ? AuthSession.useAutoDiscovery(currentUri) : null // 动态生成认证请求,依赖currentUri和discovery const [request, result, promptAsync] = AuthSession.useAuthRequest( { clientId: "ess-next", redirectUri, scopes: ['openid', 'api1'], }, discovery ) // 初始化时加载上次连接的URI useEffect(() => { setUriInput(lastConnectedUri || "") setCurrentUri(lastConnectedUri || "") }, [lastConnectedUri]) // 监听认证结果,处理成功逻辑 useEffect(() => { if (result?.type === "success") { const { authentication } = result // 将认证token存储到authStore authStore.setAuthToken(authentication.accessToken) // 可在此添加页面跳转等后续逻辑 } }, [result, authStore]) const connect = async (uri: string) => { const record = uriStore.history.filter(i => i.id === uri)[0] await uriStore.connect(record) setUriInput(uri) // 更新当前URI,触发Hook重新生成认证请求 setCurrentUri(uri) // 触发认证流程 if (promptAsync) { promptAsync() } } const connectBtn = async (uri: string) => { await uriStore.addUri(uri) connect(uri) } return ( <Box _dark={{ bg: "coolGray.800" }} _light={{ bg: "warmGray.50" }} h="100%" > <Flex px="5" pb="2" mt="50px" mb="3" mx="3" alignItems="center" borderWidth="1" borderRadius="4" borderColor="gray.300" > <Image size={150} source={appIcon} resizeMode="contain" alt="logo" /> <Text mb="3" tx="welcomeScreen.header" preset="heading" /> <Text tx="welcomeScreen.instructions" /> <Input placeholder={translate('common.uri')} h="50px" w="100%" bg="white" my="5" value={uriInput} onChangeText={setUriInput} /> <Button rounded="2" w="100%" pt="5" h="50px" mb="2" onPress={() => connectBtn(uriInput)} > {translate('common.connect')} </Button> </Flex> <Text p="3" preset="heading" tx="common.history"/> <FlatList data={[...history]} flex="1" renderItem={({item}) => <Pressable p="2" mx="3" mb="1.5" borderWidth="1" borderRadius="4" borderColor="gray.300" onPress={()=>{ connect(item.id) }}> <HStack space="3" alignItems="center"> <VStack> <Text preset="subheading">{item.id}</Text> <HStack> <Text tx="common.added"></Text> <Text>: {item.created?.toLocaleString()}</Text> </HStack> <HStack alignItems="center"> {/* <Text tx="common.lc"></Text> <Text fontSize="xs">: {item.lastConnected?.toLocaleString()}</Text> */} </HStack> </VStack> <Spacer/> <IconButton icon={<Icon as={MaterialCommunityIcons} name="delete" onPress={()=>{ uriStore.removeUri(item) }} />} _icon={{ color: "error.700", size: "lg" }}/> </HStack> </Pressable> } /> </Box> ) })
关键修改点
- 新增
currentUri状态,跟踪当前要连接的OIDC端点URI - 让
redirectUri、discovery、useAuthRequest的生成逻辑依赖currentUri,确保URI变化时自动更新配置 - 在
connect函数中更新currentUri后调用promptAsync触发认证流程 - 添加
useEffect监听认证结果,处理token存储等后续逻辑 - 处理了
currentUri为空的边界情况,避免useAutoDiscovery传入空值报错
内容的提问来源于stack exchange,提问作者maulpets
相关产品推荐
相关产品推荐

