设备码登录:没有浏览器时,CLI 和 Agent 怎么拿到令牌
2025 年之后,不少开发平台的命令行改成了设备码登录。vercel login 是我最近常碰到的一个:终端打出一串短码和一个网址,人换到手机或另一台电脑上确认,令牌再回到原来那台机器。
这套流程的正式名字是 OAuth 2.0 Device Authorization Grant,文档是 RFC 8628,2019 年 8 月发布。规范本身不新。新的是使用现场:SSH 上的服务器、CI、容器、远程 IDE,还有 Grok Bot 这类 Agent 沙箱,常常没有一块能把回调打回本机的浏览器。
RFC 8628 本来写给智能电视、媒体盒子、电子相框和打印机。它们连着网,能发出 HTTPS 请求,也能把网址和短码显示给人,但没有合适的浏览器,也没法让人舒服地输入密码。CLI 和 Agent 沙箱是同一形状。
一、一次登录实际在交换什么
设备码把两件事拆开:哪台设备将拿到令牌,以及人在哪台设备上点同意。授权服务器在中间发两份码,一份只留给设备轮询,一份只给人看。

图里灰色是发起登录的终端,紫色是要保管的码,橙色是人用的浏览器,绿色是最后拿到的令牌。后面几张图沿用这套颜色:灰是起点,紫是码和令牌,橙是浏览器或平台侧,绿是登录完成,红是核对、拒绝或停用。
要用设备码,四条得同时成立:
- 设备已经连上网。
- 设备能主动发出 HTTPS 请求。
- 设备能把网址和短码显示给用户。
- 用户另有一台电脑或手机,用来完成确认。
规范把设备要打的地址叫 device authorization endpoint,和浏览器访问的授权端点不是同一个。设备用表单 POST 上去。公开客户端带 client_id,需要权限范围时再带 scope。
人的动作很短。打开验证网址,必要时输入短码,在已经登录的会话里点同意或拒绝。点同意之前,设备反复拿到的答复是「还没批」。
轮询打的仍是原来的 token endpoint。设备不新开一个发令牌的地址。
二、响应里每份字段各走各的
下面是 RFC 8628 里的示例,不是某个厂商的真实响应。device_code 那一长串不应该出现在给人看的界面上。
{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"user_code": "WDJB-MJHT",
"verification_uri": "https://example.com/device",
"verification_uri_complete": "https://example.com/device?user_code=WDJB-MJHT",
"expires_in": 1800,
"interval": 5
}
必填的是 device_code、user_code、verification_uri 和 expires_in。interval 可选,没给就按 5 秒。verification_uri_complete 也可选,里面已经带上 user_code。
expires_in 同时管这两份码,单位是秒。示例里的 1800 是 30 分钟,各家服务会换成自己的数字。过期后这次登录作废,要重新要一组码。

device_code 是轮询凭证。熵要高,因为猜中它的人,有机会在用户批完之后领走这次授权。规范建议交互过程里不要展示它,免得和短码搞混。
user_code 必须给人看,而且要短到能念、能手打。短就意味着熵低,授权服务器应对输错次数做限速。限速和有效期一起,把暴力尝试压到不划算。
verification_uri 保持短、好记。规范不建议把 user_code 拼进这个网址,否则手打的字符串变长,打错了也不容易当场发现。带码的地址交给 verification_uri_complete,给二维码或 NFC 用。
即便展示了二维码,规范仍要求把 user_code 用文字再显示一遍。授权页也应当把短码秀出来,让人核对它和设备屏幕上是不是同一串。核对的是「我批的是眼前这台设备」。
换令牌时,设备向 token endpoint 发送固定的 grant_type:urn:ietf:params:oauth:grant-type:device_code,再加上 device_code。公开客户端还要带 client_id。
三、本地回调为什么在无头环境里断掉
另一条老路是授权码加本地回调。CLI 在本机听一个 localhost 端口,浏览器授权完跳回 http://127.0.0.1 上的这个端口,进程接过 code 再去换令牌。
笔记本上这条路很顺。命令打开系统浏览器,你点一下,窗口自己回来。

它默认两件事同时成立:这台机器能打开浏览器,授权完浏览器能访问这个端口。SSH、Docker、CI,以及没有图形界面的云主机,两条经常一起缺。浏览器在笔记本上,端口在服务器上。
SSH 隧道可以把端口转回来。能工作,但每个工具都要单独教,Agent 也不会自己去配隧道。
设备码不要求回调打回设备。第二台设备只要能打开 verification_uri,手机、公司电脑或旁边另一块屏幕都行。令牌只发给握着 device_code 的那一方。确认发生在手机上,登录态落在原来的终端里。
GitHub CLI 的 gh auth login 也提供同一类选项:终端打出一次性码,浏览器里确认。我没有写它的上线日,只把它当作已经长期存在的同类做法。
笔记本上浏览器就在手边时,本地回调少一次换设备,仍然省事。RFC 8628 写明,它不打算替换手机和桌面这类本来就有浏览器的原生应用,那些应走 RFC 8252 的原生应用实践。
四、为什么 2025 年之后特别常见
远程开发、一次性容器、云端编码 Agent,都是有 shell、没有你已经登录的浏览器。Agent 要替人部署、查日志、开预览,就得登录 Vercel 这类平台。
密码应留在浏览器里,沙箱只接收令牌。外网也打不进沙箱里临时开的回调端口。

终端出码,人在自己的浏览器里登录,核对这次请求从哪来,再让那台机器拿到当时 scope 范围内的 access_token。Grok Bot 和任何跑在服务器里的 Agent 都是这个形状:出站 HTTPS 有,入站回调没有,旁边有一台你信得过的手机。
Vercel 把设备码收成了 CLI 的主路径。更新说明 New Vercel CLI login flow 的发布日是 2025-09-12。vercel login 改为 OAuth 2.0 Device Flow,可以从任意能开浏览器的设备授权。
授权页会提醒你核对地点、IP 和请求时间。命令说明在 vercel login。

橙色是平台把登录改到设备流的那一天,红色是多数旧方式停止支持,灰色是更晚的 SAML 到期日。
同一则说明废弃了这些旧入口:vercel login email、--github、--gitlab、--bitbucket、--oob,以及 team(SAML)。2026-02-26 起,上面多数方式不再支持。team(SAML)留到 2026-06-01。
--oob 以前会把人带到 vercel.com/device。它和设备码是近亲,但不是现在这条 RFC 8628 流程,现已 obsolete。现在的 vercel login 就是设备流本身。
我把日期限制在这三处,因为它们写在 Vercel 的 changelog 里,能对上。别的云厂商 CLI 也常见设备码。没核到具体上线日之前,不把它们排进同一条时间线。
五、轮询时那几个错误码
用户还没点下去时,设备按 interval 重复请求 token endpoint。这是协议的正常部分。authorization_pending 表示码还有效、人还没做完,等满间隔再问。

slow_down 仍表示还没批,但服务器嫌你问太勤。规范要求从这次起把轮询间隔增加 5 秒,后面每一次都沿用加长后的间隔。
连接超时另算。客户端必须自己降低频率。规范建议指数退避,例如每次超时把间隔翻倍。
access_denied 表示人拒绝了,或服务器不批。expired_token 表示 device_code 过期,这次会话结束。过期后可以再要一组新码。规范建议等用户有动作再开始,避免无人值守时空转。
除了 authorization_pending 和 slow_down,别的错误码都要停止轮询,并把原因显示出来。
人点了同意之后,token endpoint 按普通成功响应返回,其中有 access_token。令牌下来的那一刻,登录才算完成。
规范原文的流程图把刷新令牌画成可选。带不带 refresh_token、有效期多长,由授权服务器决定,设备码这一步没有另外规定。
轮询可以收成几行,间隔从响应里的 interval 起算,缺省是 5 秒:
wait interval
POST token endpoint
authorization_pending -> 再等
slow_down -> interval += 5,再等
access_denied -> 停,告诉用户被拒绝
expired_token -> 停,等用户愿意时再要新码
其它错误 -> 停,并把错误显示出来
成功 -> 保存 access_token
六、优势,以及批准前要看的那一眼
优势来自约束。设备侧只需要出站 HTTPS,不用监听端口,也不把密码或会话 cookie 放进终端。密码留在你已经信任的浏览器里。
Agent 沙箱拿到的是一次授权产生的令牌,范围是当时的 scope。人可以在第二台设备上做完注册、二次验证和公司 SSO。这些步骤本来就在浏览器里,不必搬进终端。
地点、IP、请求时间也只能放在浏览器会话里核对。终端里的一行短码给不了这些信号。Vercel 把这三样写进了授权页,用的就是浏览器里已有的会话信息。
代价同样明确。短码要给人看,就可能被别人拿去用。攻击者在自己的机器上发起设备流,再诱使你打开验证页、输入他的 user_code。规范把这叫远程钓鱼。
缓解办法是授权页写明你正在授权一台设备,并给出足够的设备信息,让人能发现这不是自己眼前那一台。只扫二维码、不看页面的话,这一步会被跳过。

设备码适合一种分工:待授权的机器没有你的浏览器,批准用的浏览器是你自己的。验证页如果是别人引导你打开的仿冒站,它救不了你。
看到短码,先核对网址是不是官方的 verification_uri,再核对页面上的地点、IP、时间是不是你这次终端请求,然后再点同意。
user_code 给人,device_code 留在设备上。二维码只用 verification_uri_complete。旁边仍应留下可手打的网址和短码,给扫不了码的人一条退路。
七、什么时候该用它

笔记本上浏览器就在手边,本地回调少一次换设备,仍然省事。无头服务器、CI、远程容器、Grok Bot 这类沙箱,设备码是默认选项。
2025 年之后它变常见,是因为这种机器变多了,平台把登录主路径改到了已经存在的标准上。Vercel 从 2025-09-12 改 vercel login,并给旧方式排了 2026-02-26 和 2026-06-01 两个停用日。这是已经写明、可以核对的一个例子。