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

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函数内执行。

解决方案

  1. 用组件状态管理当前要连接的URI,用户输入或选择历史URI时更新该状态
  2. 将useAutoDiscovery和useAuthRequest放在组件顶层,依赖当前URI状态,URI变化时Hook自动重新执行,生成对应配置
  3. 在connect函数中仅更新URI状态,调用生成好的promptAsync触发认证流程
  4. 监听认证结果状态,处理成功后的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 18:45:43