本条目是一份关于 Kotlin 登录回调处理的技术避坑文档,主题是「不要通过导航传递 OAuth 回调令牌」。
用途与背景 当应用把登录交给系统浏览器时,发起登录的屏幕并没有被弹出导航栈,因为并没有发生页面跳转。浏览器重定向回来时,令牌是到达应用进程的,而不是到达某个屏幕。常见的错误做法是把令牌作为参数导航到需要它的屏幕,这会在原屏幕之上压入第二份副本;登录成功后该屏幕执行「关闭自己」,只会弹出副本,用户便落在一个一模一样的登录界面上,既无报错也无解释,表现为「已登录但卡在登录页」。
主要规则 文档给出两条必须配合使用的规则:其一,在应用入口的链接处理器中匹配回调的 scheme 与 host,取出 token 参数、先清除待处理链接,然后刻意不做任何导航,而是把令牌交给共享状态完成登录;其二,登录屏幕通过观察从持久化会话记录派生的 loggedIn 状态,在 LaunchedEffect 中调用 navigateUp 自行关闭,而不是依赖交给该屏幕的结果。屏幕是外部事实的被动观察者,因此无论谁完成登录都正确。
列出的陷阱
- 令牌返回时屏幕可能已不存在:内存受限平台上进程可能被杀并重启,回调到达冷启动的应用时已没有登录屏幕,任何假定「发起登录的屏幕仍在组合中」的写法都会失效,会话状态必须位于屏幕之上并可跨进程存活。
- 内嵌 WebView 不会触发该问题:整个流程留在进程内,屏幕确实能收到结果,因此错误只在交接系统浏览器的流程中出现,桌面宿主上尤为常见;同一代码库中多个登录里内嵌 WebView 的能正常工作,浏览器交接的不能,差异不在服务本身。
- 两种 request token 流程只有一种以重定向结束:若自行先取 token 再打开授权页,服务会认为应用已持有令牌,只渲染「可以返回应用」页面而从不调用回调,流程在最后一步静默失败,且与 scheme 注册错误难以区分;有注册回调时应选择重定向流程。
- 不要让链接规范化改写回调:负责把入站链接统一成内部形式的应用会把回调也一并改写,按原始 scheme 与 host 匹配的读取器会静默失配;应把回调形态排除在改写之外,并在通用链接处理之前匹配。
- 完成前先清除待处理链接:缓存入站 URL 的机制若不清除,会在下次组合或恢复时重放,重复提交已用过的令牌,在成功登录之上弹出虚假失败提示;先清除、后完成。
- 假定令牌一次性且短时效:它只换取一次会话,第二次交换会失败,因此任何意外重复投递都会表现为成功后出现失败,形成令人困惑的两个提示;一个投递点、一次交换。
- 没有处理器时给用户一条路:宿主完全没有 scheme handler 时,浏览器只会停在回调 URL 并显示在地址栏;登录界面上提供一个接受粘贴 URL 的输入框,复用已有解析器取出令牌并送入同一个完成函数,可把死路变成变通方案。
验证方式 文档给出三组 grep 命令:查找回调入口并确认入口到会话存储之间没有任何 navigate 调用(getQueryParameter、.scheme ==、.host == 以及 navigate();以及确认登录屏幕基于已存储状态而非传入结果关闭(LaunchedEffect(loggedIn) 或 LaunchedEffect(isLoggedIn)),均排除 /build/ 目录。行为上需按三条代码路径分别测试登录:应用仍在前台时完成、后台停留到进程被杀后完成、手动粘贴回调 URL 完成;三者都必须回到用户进入登录屏幕之前的页面,若任一落在登录页,应检查返回栈中的重复条目而非登录本身。
限制与未提供内容 文档为示例性避坑说明,代码中的名称已做泛化处理,未提供具体库、版本或完整可运行工程;平台与 URL 等细节以原文为准。