Expo结合Electron实现OAuth2登录时重定向后流程中断问题求助
问题根因
你当前的写法默认适配移动端/纯Web场景,Electron环境下Expo的AuthSession默认会把重定向URI当成普通Web地址打开,没有对接Electron的自定义窗口拦截逻辑,因此会出现登录后留在新窗口、无法回原应用的问题。
分步解决方案
1. 调整AuthScreen的环境判断逻辑
首先区分Electron环境,单独处理重定向地址和登录逻辑:
// 新增Electron环境判断 const isElectron = navigator.userAgent.toLowerCase().includes('electron'); const useProxy = Platform.OS !== 'web' && !isElectron; export function AuthScreen() { const {nextState} = useAuth(); const discovery = AuthSession.useAutoDiscovery('https://my-open-id.url'); // 重定向地址单独处理,Electron用自定义协议 const redirectUri = isElectron ? 'myapp://auth/callback' // 此处和后续Electron主进程注册的回调地址保持一致 : AuthSession.makeRedirectUri({useProxy}); // Create and load an auth request const [request, , promptAsync] = AuthSession.useAuthRequest( { clientId: 'client-id', // 注意:生产环境不要把clientSecret写在前端代码中,建议放到Electron主进程或后端处理 clientSecret: 'client-secret', redirectUri, scopes: ['openid', 'profile', 'email', 'offline_access'], }, discovery ); const login = useCallback(async () => { // Electron环境单独走自定义登录逻辑 if (isElectron) { // 监听Electron主进程返回的授权回调地址 window.electronAPI.onAuthCallback(async (callbackUrl) => { // 手动解析授权结果 const result = await AuthSession.parseLoginResponse(request, discovery, callbackUrl) if (result.type === 'success') { const token = result.authentication; nextState(token); } }) // 手动打开授权页 window.open(request.url); return; } // 其他平台原有逻辑保持不变 const result = await promptAsync({useProxy}); console.log({result}); if (!result) { return; } switch (result.type) { case "cancel": case "dismiss": return; case "error": return; case "locked": return; case "success": const token = result.authentication; nextState(token); return; } }, [promptAsync, request, discovery, nextState]); if (Platform.OS !== 'web') { useEffect(() => { WebBrowser.warmUpAsync(); return () => { WebBrowser.coolDownAsync(); } }, []); } return ( <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}> <Text>Auth Screen</Text> <Button title="Login!" disabled={!request} onPress={login}/> </View> ); }
2. 新增Electron预加载脚本
在Electron项目中新增preload.js,用来暴露主进程通信能力给渲染层:
const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electronAPI', { onAuthCallback: (callback) => ipcRenderer.on('auth-callback', (_, url) => callback(url)) })
3. 调整Electron主进程逻辑
在Electron主进程代码中添加自定义协议注册、窗口拦截、回调转发逻辑:
const { app, BrowserWindow, protocol } = require('electron'); const path = require('path'); // 注册自定义协议 app.setAsDefaultProtocolClient('myapp'); let mainWindow; function createWindow () { mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), // 关联上面的预加载脚本 contextIsolation: true, enableRemoteModule: false, nodeIntegration: false } }) // 加载Expo Web打包后的产物地址 mainWindow.loadURL('你的Expo Web部署地址/本地文件路径') } // 拦截自定义协议回调,转发给主窗口并关闭授权页 app.on('open-url', (event, url) => { event.preventDefault() mainWindow.webContents.send('auth-callback', url) // 关闭授权窗口 BrowserWindow.getAllWindows().forEach(win => { if (win !== mainWindow) win.close() }) }) // 拦截新窗口打开请求,仅放行OAuth授权地址 app.on('web-contents-created', (event, contents) => { contents.setWindowOpenHandler(({ url }) => { if (url.startsWith('你的OAuth服务端地址前缀')) { return { action: 'allow', overrideBrowserWindowOptions: { width: 600, height: 800, parent: mainWindow, modal: true } } } return { action: 'deny' } }) })
注意事项
- OAuth服务端的授权回调白名单需要添加自定义协议地址
myapp://auth/callback - Windows平台下自定义协议注册可能需要额外处理注册表逻辑,确保打包后协议能正常唤起应用
- 生产环境禁止把clientSecret硬编码在前端代码中,建议转移到Electron主进程或后端服务处理,避免泄露
内容的提问来源于stack exchange,提问作者Selast Lambou
相关产品推荐
相关产品推荐

