原生流程
Connect 浏览器流程假设你能把浏览器跳转到 via.sudomimus.com,并接收回调。对 Web 应用来说没问题,但对桌面应用、游戏和无头工具来说就尴尬了。
根据客户端的能力,原生客户端有三种流程可选。其中两种是 Native API(native-api.sudomimus.com)的一次性 direct-issue 端点——Steam direct-issue 和 AccessKey direct-issue。第三种「浏览器轮询」则是一条 Connect API 流程(/establish → /status-poll → /redeem),由原生客户端自行驱动,并不运行在 Native API 上。
如果你的客户端是公共客户端,不应该拿到应用 client-auth 私钥,也不应该预置 AccessKey secret,请改用设备码授权。设备码授权才是面向 CLI、启动器等公共客户端的验证码确认流程。
| 流程 | 适用场景 | 往返次数 | 端点 |
|---|---|---|---|
| 浏览器轮询 | 任何能拉起系统浏览器的原生客户端 | 3+ (establish + N×poll + redeem) | connect POST /establish → /status-poll → /redeem |
| Steam direct-issue | 通过 Steam 发行、能调用 Steamworks SDK 的游戏 | 1 | native-api POST /direct-issue/steam-ticket |
| AccessKey direct-issue | 预先拿到应用级凭据、绑定到已存在账户的 CLI / 无头服务 / 启动器 | 1 | native-api POST /direct-issue/access-key |
当原生客户端能拉起用户的系统浏览器、但又不方便接收回调 URL 时,使用轮询流程:
- 客户端后端调用
connect POST /establish(带上应用的 client-auth JWT),声明STATUS_POLL返回方法,拿到{ exposureKey, hiddenKey }。 - 客户端拉起系统浏览器并打开
https://via.sudomimus.com/?exposure-key=<exposureKey>。 - 用户在浏览器中完成通行密钥或邮箱验证码挑战。
- 客户端每隔几秒调用一次
connect POST /status-poll,提交{ exposureKey, hiddenKey }。一旦用户完成,轮询会返回{ status: "REALIZED", confirmationKey }。 - 客户端将三个密钥提交给
connect POST /redeem,换取{ accessToken, refreshToken }。
这一方案适用于任何带默认浏览器的平台——Windows、macOS、Linux 桌面应用、Electron 等等。应用的 Layer 3 规则中必须允许 STATUS_POLL。
/establish 就是标准的、用 client-auth 签名的 Connect 请求 —— 完整形状见 Web 应用 —— 只是返回方法换成 STATUS_POLL:
curl -X POST https://connect-api.sudomimus.com/establish \ -H "Content-Type: application/json" \ -H "Authorization: SudomimusClientJWT $SUDOMIMUS_CLIENT_AUTH_JWT" \ -d '{ "applicationAnchor": "your-application", "returnMethods": [ { "type": "STATUS_POLL", "payload": {} } ] }'# → { "exposureKey": "exp_...", "hiddenKey": "hid_..." }随后用这两把 key 每隔几秒轮询一次 /status-poll。轮询不带 client-auth JWT —— 授权它的是对 hiddenKey 的持有:
curl -X POST https://connect-api.sudomimus.com/status-poll \ -H "Content-Type: application/json" \ -d '{ "exposureKey": "exp_...", "hiddenKey": "hid_..." }'
# 用户仍在浏览器里鉴权时:# { "status": "PENDING" }# 用户完成后:# { "status": "REALIZED", "confirmationKey": "cnf_..." }轮询返回 REALIZED 后,将三个密钥提交给 connect POST /redeem,换取访问令牌和刷新令牌。该调用与 Web 流程中的 /redeem 相同。
Steam direct-issue
Section titled “Steam direct-issue”对于通过 Steam 发行的游戏,Sudomimus 支持一种完全不打开浏览器的静默登录。用户看不到任何登录提示;他们的 Steam 身份会直接换成一个 Sudomimus 会话。
curl -X POST https://native-api.sudomimus.com/direct-issue/steam-ticket \ -H "Content-Type: application/json" \ -d '{ "applicationAnchor": "my-game", "steamTicketHex": "<来自 Steamworks GetAuthTicketForWebApi 的 hex 编码 ticket>", "steamAppId": 480 }'流程:
- 游戏调用 Steamworks 的
ISteamUser::GetAuthTicketForWebApi("sudomimus")——不要用GetAuthSessionTicket,两者是不同类型的 ticket,不能互换。identity 字符串必须是"sudomimus"(大小写敏感);其他值会被拒绝。 - 游戏等待
GetTicketForWebApiResponse_t回调,再使用该 ticket。 - 把 ticket 字节流 hex 编码后作为
steamTicketHex,连同applicationAnchor和steamAppId一起 POST 到/direct-issue/steam-ticket。 - Sudomimus 向 Steam 校验 ticket,查找或创建账户,然后 —— 在顺利路径上 —— 在同一次请求中返回
{ accessToken, refreshToken }。如果应用要求 Steam 账户尚未提供的同意或资料,这一步会改为返回一个带 Errand 交接的403—— 见当 direct-issue 需要同意或资料时。 - 拿到令牌后,游戏调用
Steamworks.CancelAuthTicket(handle)收尾。
整个过程用户都不会离开游戏。Steam 账户就是身份来源。
Steam 流程的应用配置
Section titled “Steam 流程的应用配置”应用必须配置:
- Layer 1:一条
STEAM_TICKETAuthenticationRule,allowedSteamAppIds: number[]中包含该游戏的 Steam App ID。 - Layer 2:至少一条能命中的规则——通常是
STEAM_ID,allowedSteamIds: ["*"]表示放行任何已验证的 Steam 账户,或者给出一组具体的 SteamID64 字符串。 - Layer 3:一条
DIRECT_ISSUEReturnRule。
从未绑定过邮箱的「Steam-first」账户必须依赖 STEAM_ID(或 ACCOUNT_ALIAS / SECTOR_SUBJECT)类型的 Layer 2 规则;只有 EMAIL 规则会把它拒掉。
该端点不需要 client-auth JWT——Steam ticket 本身就证明了用户身份以及这个二进制有权与该应用对话。
对应的 Web 流程。 浏览器中的「Sign in with Steam」按钮使用 Layer 1 的
STEAM_OPENID,而不是STEAM_TICKET。两条路径都会解析为同一个用户级 Steam 身份,因此用户先从游戏登录后,也可以继续使用网页按钮登录,反之亦然,无需合并账户。STEAM_OPENID的规则结构见认证规则。
AccessKey direct-issue
Section titled “AccessKey direct-issue”适用于没有 Steam ticket、但目标 Sudomimus 账户已经明确的场景——CLI 工具、自定义启动器、无头服务、自动化测试。「证明」是由 Sudomimus 签发的一对凭据(accessKeyIdentifier + accessKeySecret),在开发者门户中生成后通过线下方式交付给运维方。
curl -X POST https://native-api.sudomimus.com/direct-issue/access-key \ -H "Content-Type: application/json" \ -d '{ "applicationAnchor": "my-cli-tool", "accessKeyIdentifier": "acs_k_<uuidv4>", "accessKeySecret": "acs_t_<64 位小写 hex>" }'两个凭据字符串都带有强制前缀:
acs_k_—— 公开 identifier,后接 UUIDv4。acs_t_—— 密文部分,后接 64 位小写 hex(在创建时只展示一次,之后无法恢复)。
前缀是规范形式的一部分。它们让两个部分在视觉上可区分,也方便 secret scanner 通过字面子串匹配到误提交的凭据。
AccessKey 流程的应用配置
Section titled “AccessKey 流程的应用配置”应用必须配置:
- Layer 1:一条
ACCESS_KEY_DIRECTAuthenticationRule(空 payload)。不显式开启则默认拒绝。 - Layer 2:一条能匹配目标账户的规则——
EMAIL、STEAM_ID、ACCOUNT_ALIAS或SECTOR_SUBJECT。 - Layer 3:一条
DIRECT_ISSUEReturnRule。
AccessKey 凭据无法创建新账户。 凭据绑定在一个已存在的 Sudomimus 账户上;该账户被删除后,所有与之绑定的凭据在登录时都会被拒绝。
凭据通过 with.sudomimus.com 中应用详情页的 Access keys Tab 管理。吊销是软删除(revokedAt 时间戳);轮换 = 吊销 + 重新签发。过期凭据不会自动清理,但在 handler 处会被拒绝。
该端点也不需要 client-auth JWT——access-key 的密文本身就是凭据。把 client-auth 私钥嵌进发行给运维的 CLI 里,任何人都能反编译出来,因此再叠一层并不带来真正的防御。
当 direct-issue 需要同意或资料时
Section titled “当 direct-issue 需要同意或资料时”两个 direct-issue 端点都是一次性的读取者——它们没法弹出同意界面,也没法让用户敲入一封邮箱。所以当应用要求一条用户尚未授权的声明(claim),或要求账户还没有的数据(例如纯 Steam 账户没有邮箱)时,这次调用不能就这么成功。它会改为返回一个带 Errand 的 403 —— 一段短暂的浏览器侧行,用户在那里完成这些工作:
{ "reason": "ClaimConsentRequired", "claims": { "email": { "requirement": "REQUIRED", "state": "UNKNOWN" }, "firstName": { "requirement": "OPTIONAL", "state": "UNKNOWN" }, "lastName": { "requirement": "OFF", "state": "UNKNOWN" } }, "errand": { "errandKey": "ernd_...", "url": "https://via.sudomimus.com/errand?key=ernd_...", "expiresAt": "2026-06-10T12:30:00Z" }}reason 是 ClaimConsentRequired(用户必须同意共享某条 required 声明)或 RequiredClaimDataMissing(同意已就位,但账户数据缺失)二者之一。Steam 与 AccessKey direct-issue 在这里行为完全一致。补救方式:
- 在用户的系统浏览器中打开
errand.url。页面会引导用户完成所有待办的登录、数据录入和同意。 - 每隔约 2 秒轮询
GET /errand/{errandKey}/status(native-api),直到它报告COMPLETED—— 或者干脆让用户在你的 UI 里告诉你他完成了。 - 把同一个 direct-issue 调用重试一次。它现在会成功返回
{ accessToken, refreshToken }。
curl https://native-api.sudomimus.com/errand/ernd_.../status# → { "status": "PENDING" } 用户仍在浏览器里操作# → { "status": "COMPLETED" } 完成 —— 重试 direct-issue# → { "status": "EXPIRED" } 已过期/已消费/未知 —— 重新跑 direct-issue 拿一个新的 errand任一端点的 200 也会带一个 claims 块(形状与 403 里的相同),所以即便成功,你也能看出哪些可选声明被共享了、哪些被保留了。完整契约 —— 30 分钟存活期、为什么重试会复用同一个 errandKey、以及两种安全层级(仅同意 vs. 需要登录)—— 见 Errand。
| 你的客户端是… | 用 |
|---|---|
| Web 应用 | Connect + via.sudomimus.com(常规流程) |
| 能拉起浏览器的桌面应用 / Electron / 移动端 | Connect + 通过系统浏览器打开 via.sudomimus.com + connect /status-poll |
| 没有 client secret 的公共 CLI / 启动器 | 设备码授权 |
| 通过 Steam 发行的游戏 | native-api POST /direct-issue/steam-ticket(静默,一次往返) |
| 通过预签发凭据绑定到已知账户的 CLI / 无头服务 | native-api POST /direct-issue/access-key(一次往返,使用 AccessKey) |
无论走哪条路径,最终返回的令牌格式都是一样的——你的应用只需要处理同一种 access token 结构,与用户的认证方式无关。