通过自定义界面实现单点登录延续¶
当终端用户的浏览器中已经存在有效的 Authany 会话时,您的自定义界面可以显示“以 user@example.com 身份继续”选项,而不必要求用户重新输入凭据。这一能力由 select_account 提供,它是一种可添加到 login 和 signup_login 流程的 identify 步骤中的身份识别选项。
select_account 通过您的自定义界面已经在调用的 Authentication Flow API 端点工作,没有新增端点,也没有新增令牌。当浏览器存在符合条件的会话时,identify 步骤的响应会包含一个描述该账号的额外选项;如果没有符合条件的会话,响应保持不变,因此即使自定义界面不识别该选项,也能像之前一样正常工作。
如果您是初次构建自定义界面,请先阅读 Authentication Flow API 概述。
使用场景¶
同一项目内多个应用间的单点登录。 应用 A 和应用 B 是同一个 Authany 项目下的两个 OAuth 客户端,并共用同一个自定义界面。用户登录应用 A 后,首次在同一浏览器中打开应用 B。由于 Authany 会话 Cookie 是共享的,自定义界面可以提供以同一账号继续的选项。用户只需点击一次,而无需再次输入凭据。
单一的“继续”入口。 您的自定义界面使用一个合并页面(signup_login 流程),而不是分开的登录页和注册页。当会话已经存在时,该页面会在常规的邮箱和社交登录选项旁显示“以 user@example.com 身份继续”。如果用户想拒绝,只需选择其他选项,甚至可以借此注册第二个账号。
前置条件¶
- 您的自定义界面必须与 Authany 端点同站(same-site)。 只有当您的自定义界面与 Authany 端点共享同一个可注册域名(即 eTLD+1,例如
example.com,比如auth.example.com和ui.example.com)时,浏览器才会将 Authany 会话 Cookie 发送给 Authentication Flow API。实际操作中,这意味着需要为您的项目设置自定义域名。托管在无关域名上的自定义界面永远不会看到select_account选项。 - 已配置自定义界面 URI。 您的 OAuth 客户端必须在 Authany 管理台中设置自定义界面 URI(应用 > 您的应用 > 自定义界面)。请注意,同站(same-site)不等同于同源(same-origin):
ui.example.com和auth.example.com仍然是跨源的,因此浏览器会对这些 API 调用应用 CORS。Authany 允许来自已注册为自定义界面 URI 的源发起带凭据的跨源请求,因此您无需额外配置 CORS。 - 从浏览器发送会话 Cookie。 使用
fetch()调用 Authentication Flow API 时必须设置credentials: 'include',否则不会发送会话 Cookie,该选项也永远不会出现。
flowchart TD
appa["App A"] --> ui
appb["App B"] --> ui
subgraph samesite ["example.com — same registrable domain"]
ui["Shared Custom UI<br/>ui.example.com"]
authgear["Authgear<br/>auth.example.com"]
end
other["Custom UI on an unrelated domain<br/>ui.other-domain.io"]
ui -- "session cookie sent,<br/>select_account offered" --> authgear
other -. "cookie never sent,<br/>option never appears" .-> authgear
说明
如果您的自定义界面有各自独立的登录和注册页面,其首次创建流程的调用类型必须是 login 或 signup_login,绝不能直接是 signup。select_account 只存在于这两种流程类型中,因此如果一开始就使用 signup,您将永远无法得知是否存在会话。只有当用户没有符合条件的会话(或拒绝了该会话)并且想要注册新账号时,才需要之后再单独创建 signup 流程。
工作原理¶
select-account 的交互过程内嵌在每个自定义界面已经遵循的同一套 OAuth 流程之中。唯一新增的部分是 identify 响应中的额外选项,以及用于完成该选项的一键式输入:
sequenceDiagram
participant App as Your App
participant CustomUI as Custom UI (ui.example.com)
participant Authgear as Authgear (auth.example.com)
Note over App,Authgear: The browser already holds an Authgear session cookie
App->>Authgear: GET /oauth2/authorize?client_id=...
Authgear-->>CustomUI: 302 to Custom UI URI (client_id, x_ref, ...)
CustomUI->>Authgear: POST /api/v1/authentication_flows<br/>fetch with credentials: 'include' — cookie sent (same-site)
Authgear-->>CustomUI: identify options include select_account<br/>(display_name, user_id)
Note over CustomUI: Render "Continue as user@example.com"
CustomUI->>Authgear: POST /api/v1/authentication_flows/states/input<br/>{ "identification": "select_account", "index": 0 }
Authgear-->>CustomUI: action.type: finished (finish_redirect_uri)
CustomUI->>Authgear: Top-level navigation to finish_redirect_uri
Authgear-->>App: 302 to redirect_uri with authorization code
App->>Authgear: POST /oauth2/token (exchange code for tokens)
用户全程无需输入任何凭据,唯一的操作就是点击“以 user@example.com 身份继续”。如果没有符合条件的会话,创建流程的响应中不会包含 select_account 条目,您的界面会按照普通登录流程继续。
步骤 1:将 select_account 添加到您的流程配置中¶
在 Authany 管理台中,进入高级 > 编辑配置,在 authentication_flow 部分下,将 select_account 添加到登录流程的 identify 步骤中:
上面的 select_account 条目没有嵌套的 steps,因此选择它会立即完成登录。如果想先要求提供额外信息(例如 2FA 验证码),可以为该条目添加一个嵌套的 authenticate 步骤。参见下文的在延续登录时要求 2FA。
步骤 2:在自定义界面中检测该选项¶
像往常一样创建流程,并通过 url_query 转发 Authany 传递给您自定义界面 URI 的查询参数(尤其是 client_id 和 x_ref):
const response = await fetch("https://auth.example.com/api/v1/authentication_flows", {
method: "POST",
credentials: "include", // required: sends the Authgear session cookie
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "login",
name: "default",
url_query: window.location.search.substring(1),
}),
});
当浏览器存在符合条件的会话时,identify 步骤的 options 中会包含一个 select_account 条目,其中带有该账号的显示名称和用户 ID:
{
"result": {
"state_token": "authflowstate_VGHZ8SBCKGZK2KW84TCAKWGM8QZH0B69",
"type": "login",
"name": "default",
"action": {
"type": "identify",
"data": {
"type": "identification_data",
"options": [
{
"identification": "select_account",
"display_name": "user@example.com",
"user_id": "user_01J8ZC0M2N3P4Q5R6S7T8V9W0X"
},
{
"identification": "email"
}
]
}
}
}
}
使用 display_name 来渲染“以 user@example.com 身份继续”按钮。与本 API 其他地方的 masked_display_name 不同,该字段不会被脱敏返回,因为它标识的是已经绑定在调用方自身会话 Cookie 上的账号。
当没有符合条件的会话时,select_account 条目不会出现,响应的其余部分保持不变。
步骤 3:提交所选项¶
当用户点击“以…身份继续”时,通过该选项的 index(即它在 options 数组中的位置)提交该选项:
{
"state_token": "authflowstate_VGHZ8SBCKGZK2KW84TCAKWGM8QZH0B69",
"input": {
"identification": "select_account",
"index": 0
}
}
使用步骤 1 中的最小配置,该流程会立即完成:
{
"result": {
"state_token": "authflowstate_ABCJVB0IJKLQ2S1K2G34RX56R1C1E789",
"type": "login",
"name": "default",
"action": {
"type": "finished",
"data": {
"finish_redirect_uri": "https://auth.example.com/oauth2/consent?..."
}
}
}
}
通过顶层导航(而不是 fetch() 调用)将浏览器重定向到 finish_redirect_uri,方式与其他已完成的流程完全相同。
说明
user_id 仅用于展示,是只读的。输入内容始终只携带 index;服务器会在提交时根据自身的会话 Cookie 重新解析账号,因此伪造或过期的 user_id 永远无法用来选择其他账号。
步骤 4:处理用户拒绝的情况¶
没有专门的“拒绝”输入项。如果用户想以其他身份登录,可以改为提交其他任意选项的输入,例如:
{
"state_token": "authflowstate_VGHZ8SBCKGZK2KW84TCAKWGM8QZH0B69",
"input": {
"identification": "email",
"login_id": "another-user@example.com"
}
}
之后该流程会完全按照普通登录继续进行。
在 signup_login 流程中使用 select_account¶
在 signup_login 流程中,select_account 只声明一个 login_flow;它只能延续已有的登录,永远不能用于注册。选择它会切换到指定的登录流程,并在其中重放同一个 identify 输入,因此该登录流程自身也必须声明一个 select_account 条目。如果没有,重放的输入将无法匹配目标流程中的任何选项,提交会失败而不是完成登录:
目标登录流程中在 identify 之后配置的任何步骤(例如 terminate_other_sessions)仍会执行;通过 select_account 延续登录不会跳过这些步骤。
在延续登录时要求 2FA¶
如果想在通过已有会话延续登录时要求提供新的第二因素,同时不为普通登录添加该步骤,可以为 select_account 条目添加一个专属的嵌套 authenticate 步骤:
此时提交 select_account 会返回一个 authenticate 操作,而不是直接完成:
{
"result": {
"state_token": "authflowstate_XYZ3JVB0IJKLQ2S1K2G34RX56R1C1E78",
"type": "login",
"name": "default",
"action": {
"type": "authenticate",
"authentication": "secondary_totp",
"data": {
"type": "authentication_data",
"options": [
{ "authentication": "secondary_totp" }
]
}
}
}
}
像往常一样提交 TOTP 验证码即可完成该流程。
强制使用 login_hint 或 id_token_hint¶
如果您的应用程序在授权请求中传递了 login_hint 或 id_token_hint,Authany 会在重定向到您自定义界面 URI 时,将这两个参数与 client_id 和 x_ref 一并转发。
Authentication Flow API 特意不会根据这些提示(hint)过滤 select_account 选项;只要存在符合条件的会话,该选项就会被提供。如果您希望仅在匹配提示时才提供延续登录,需要在自定义界面中自行实现该检查:自行解析提示、将其与该选项的 user_id 进行比对,若不匹配则隐藏该选项,并回退到没有符合条件会话时界面的处理方式。对于 id_token_hint,这意味着需要解码 ID 令牌,并将其 sub 声明与该选项的 user_id 进行比对。如果自定义界面不关心这些提示,可以完全忽略 user_id。
错误处理¶
如果在选项显示之后、输入提交之前会话发生了变化(例如用户在另一个标签页中登出或切换了账号),API 会返回:
{
"error": {
"name": "Unauthorized",
"reason": "SelectAccountSessionChanged",
"message": "session no longer matches the selected account",
"code": 401
}
}
处理方式是重新启动流程:创建一个新的身份验证流程,并渲染最新响应中包含的选项。
该选项何时不会出现¶
在以下任一情况成立时,select_account 选项都不会出现,响应会与未配置该功能时相同:
- 不存在有效会话,或未发送会话 Cookie(缺少
credentials: 'include',或自定义界面与 Authany 不同站)。 - 流程配置的
identify步骤中未列出select_account。 - 授权请求携带了
prompt=login,或其max_age已过期(Authany 会将其视为prompt=login)。 - 该会话是在禁用 SSO 的情况下建立的——例如 SDK 配置了
isSSOEnabled: false,这会抑制共享会话 Cookie(参见移动应用 / 网站的 SSO)。
注意
授权请求中的 prompt=select_account 与本功能无关,尽管名称相同,但不会产生任何效果。