# 设备码登录：没有浏览器时，CLI 和 Agent 怎么拿到令牌

> RFC 8628 让终端出示短码，人在另一台设备上确认后再发令牌。Vercel CLI 从 2025-09-12 改走设备码，无头服务器和 Agent 沙箱因此不用本地回调也能登录。

- 发布于: 2026-10-18
- 原文: https://shanyue.tech/posts/device-code-oauth/
- 标签: oauth, device-code, rfc-8628, vercel, ai

---

2025 年之后，不少开发平台的命令行改成了设备码登录。`vercel login` 是我最近常碰到的一个：终端打出一串短码和一个网址，人换到手机或另一台电脑上确认，令牌再回到原来那台机器。

这套流程的正式名字是 OAuth 2.0 Device Authorization Grant，文档是 [RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628/)，2019 年 8 月发布。规范本身不新。新的是使用现场：SSH 上的服务器、CI、容器、远程 IDE，还有 `Grok Bot` 这类 `Agent` 沙箱，常常没有一块能把回调打回本机的浏览器。

`RFC 8628` 本来写给智能电视、媒体盒子、电子相框和打印机。它们连着网，能发出 HTTPS 请求，也能把网址和短码显示给人，但没有合适的浏览器，也没法让人舒服地输入密码。CLI 和 `Agent` 沙箱是同一形状。

## 一、一次登录实际在交换什么

设备码把两件事拆开：哪台设备将拿到令牌，以及人在哪台设备上点同意。授权服务器在中间发两份码，一份只留给设备轮询，一份只给人看。

![设备码登录总览：终端向授权端点要码，留下 device_code，把 user_code 和验证网址给人，人在另一台设备的浏览器里确认，设备再轮询换到 access_token](https://shanyue.tech/images/26-10-18/device-code-overview.webp)

图里灰色是发起登录的终端，紫色是要保管的码，橙色是人用的浏览器，绿色是最后拿到的令牌。后面几张图沿用这套颜色：灰是起点，紫是码和令牌，橙是浏览器或平台侧，绿是登录完成，红是核对、拒绝或停用。

要用设备码，四条得同时成立：

- 设备已经连上网。
- 设备能主动发出 HTTPS 请求。
- 设备能把网址和短码显示给用户。
- 用户另有一台电脑或手机，用来完成确认。

规范把设备要打的地址叫 `device authorization endpoint`，和浏览器访问的授权端点不是同一个。设备用表单 POST 上去。公开客户端带 `client_id`，需要权限范围时再带 `scope`。

人的动作很短。打开验证网址，必要时输入短码，在已经登录的会话里点同意或拒绝。点同意之前，设备反复拿到的答复是「还没批」。

轮询打的仍是原来的 `token endpoint`。设备不新开一个发令牌的地址。

## 二、响应里每份字段各走各的

下面是 `RFC 8628` 里的示例，不是某个厂商的真实响应。`device_code` 那一长串不应该出现在给人看的界面上。

```json
{
  "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，device_code 留在设备上轮询，user_code 只配验证网址给人，人在页面上核对短码](https://shanyue.tech/images/26-10-18/device-code-two-codes.webp)

`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` 再去换令牌。

笔记本上这条路很顺。命令打开系统浏览器，你点一下，窗口自己回来。

![本地回调和设备码：本机要能打开浏览器并把回调打回 localhost，无头环境这条路会断；设备码改由第二台设备确认，令牌仍回到发起登录的 CLI](https://shanyue.tech/images/26-10-18/device-code-vs-callback.webp)

它默认两件事同时成立：这台机器能打开浏览器，授权完浏览器能访问这个端口。SSH、Docker、CI，以及没有图形界面的云主机，两条经常一起缺。浏览器在笔记本上，端口在服务器上。

SSH 隧道可以把端口转回来。能工作，但每个工具都要单独教，`Agent` 也不会自己去配隧道。

设备码不要求回调打回设备。第二台设备只要能打开 `verification_uri`，手机、公司电脑或旁边另一块屏幕都行。令牌只发给握着 `device_code` 的那一方。确认发生在手机上，登录态落在原来的终端里。

`GitHub CLI` 的 `gh auth login` 也提供同一类选项：终端打出一次性码，浏览器里确认。我没有写它的上线日，只把它当作已经长期存在的同类做法。

笔记本上浏览器就在手边时，本地回调少一次换设备，仍然省事。`RFC 8628` 写明，它不打算替换手机和桌面这类本来就有浏览器的原生应用，那些应走 `RFC 8252` 的原生应用实践。

## 四、为什么 2025 年之后特别常见

远程开发、一次性容器、云端编码 `Agent`，都是有 shell、没有你已经登录的浏览器。`Agent` 要替人部署、查日志、开预览，就得登录 `Vercel` 这类平台。

密码应留在浏览器里，沙箱只接收令牌。外网也打不进沙箱里临时开的回调端口。

![无头服务器和 Agent：沙箱里的终端打印 user_code 和验证网址，人在手机浏览器里批准，access_token 回到沙箱，密码不进设备](https://shanyue.tech/images/26-10-18/device-code-headless.webp)

终端出码，人在自己的浏览器里登录，核对这次请求从哪来，再让那台机器拿到当时 `scope` 范围内的 `access_token`。`Grok Bot` 和任何跑在服务器里的 `Agent` 都是这个形状：出站 HTTPS 有，入站回调没有，旁边有一台你信得过的手机。

`Vercel` 把设备码收成了 CLI 的主路径。更新说明 [New Vercel CLI login flow](https://vercel.com/changelog/new-vercel-cli-login-flow) 的发布日是 2025-09-12。`vercel login` 改为 OAuth 2.0 Device Flow，可以从任意能开浏览器的设备授权。

授权页会提醒你核对地点、IP 和请求时间。命令说明在 [vercel login](https://vercel.com/docs/cli/login)。

![Vercel CLI 登录时间线：2025-09-12 起 vercel login 改用设备流，2026-02-26 起邮箱、Git 标志和 oob 等旧方式停用，team 的 SAML 保留到 2026-06-01](https://shanyue.tech/images/26-10-18/device-code-vercel.webp)

橙色是平台把登录改到设备流的那一天，红色是多数旧方式停止支持，灰色是更晚的 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` 表示码还有效、人还没做完，等满间隔再问。

![轮询 token 端点：authorization_pending 表示继续等，slow_down 要求把间隔加 5 秒，用户同意后才返回 access_token](https://shanyue.tech/images/26-10-18/device-code-poll.webp)

`slow_down` 仍表示还没批，但服务器嫌你问太勤。规范要求从这次起把轮询间隔增加 5 秒，后面每一次都沿用加长后的间隔。

连接超时另算。客户端必须自己降低频率。规范建议指数退避，例如每次超时把间隔翻倍。

`access_denied` 表示人拒绝了，或服务器不批。`expired_token` 表示 `device_code` 过期，这次会话结束。过期后可以再要一组新码。规范建议等用户有动作再开始，避免无人值守时空转。

除了 `authorization_pending` 和 `slow_down`，别的错误码都要停止轮询，并把原因显示出来。

人点了同意之后，`token endpoint` 按普通成功响应返回，其中有 `access_token`。令牌下来的那一刻，登录才算完成。

规范原文的流程图把刷新令牌画成可选。带不带 `refresh_token`、有效期多长，由授权服务器决定，设备码这一步没有另外规定。

轮询可以收成几行，间隔从响应里的 `interval` 起算，缺省是 5 秒：

```text
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`。规范把这叫远程钓鱼。

缓解办法是授权页写明你正在授权一台设备，并给出足够的设备信息，让人能发现这不是自己眼前那一台。只扫二维码、不看页面的话，这一步会被跳过。

![批准前的核对：验证页展示 user_code，人核对地点、IP 和请求时间，对不上就拒绝，对得上设备才拿到令牌](https://shanyue.tech/images/26-10-18/device-code-check.webp)

设备码适合一种分工：待授权的机器没有你的浏览器，批准用的浏览器是你自己的。验证页如果是别人引导你打开的仿冒站，它救不了你。

看到短码，先核对网址是不是官方的 `verification_uri`，再核对页面上的地点、IP、时间是不是你这次终端请求，然后再点同意。

`user_code` 给人，`device_code` 留在设备上。二维码只用 `verification_uri_complete`。旁边仍应留下可手打的网址和短码，给扫不了码的人一条退路。

## 七、什么时候该用它

![什么时候用设备码：这台机器开得开你的浏览器就用本地回调，SSH、CI 和 Agent 沙箱走设备码](https://shanyue.tech/images/26-10-18/device-code-when.webp)

笔记本上浏览器就在手边，本地回调少一次换设备，仍然省事。无头服务器、CI、远程容器、`Grok Bot` 这类沙箱，设备码是默认选项。

2025 年之后它变常见，是因为这种机器变多了，平台把登录主路径改到了已经存在的标准上。`Vercel` 从 2025-09-12 改 `vercel login`，并给旧方式排了 2026-02-26 和 2026-06-01 两个停用日。这是已经写明、可以核对的一个例子。
