OAuth Device Flow 简明指南:CLI 如何通过浏览器完成授权

随着 AI Agent 和开发者工具的发展,CLI 正重新成为常见的交互与集成入口。GitHub CLI gh、Lark/Feishu lark-cli、Codex CLI 等工具在调用云端 API 时,都需要完成用户授权,而 OAuth Device Flow 正是这类场景中常见的一种方式。

OAuth Device Flow,也叫 Device Authorization Flow,主要用于那些不方便打开浏览器、不适合输入密码,或者无法接收 redirect URI 回调的设备和工具,例如智能电视、游戏机、IoT 设备、服务器终端和 CLI 工具。

它的核心思路可以概括为:

设备不直接让用户输入密码,而是显示一个短码;用户用手机或电脑打开浏览器完成登录授权;设备在后台轮询,直到拿到 access token。

它解决什么问题

普通 OAuth Authorization Code Flow 通常依赖浏览器跳转和 redirect URI。桌面应用可以在本机启动一个回调服务,Web 应用可以接收服务器回调,但很多设备做不到:

  • 电视、游戏机输入账号密码很麻烦;
  • CLI 工具可能运行在没有浏览器的 SSH 环境里;
  • IoT 设备通常没有完整浏览器,也没有稳定公网回调地址;
  • 用户密码和 MFA 不应该输入到不可信或输入体验差的设备上。

Device Flow 的作用,就是把登录和授权动作转移到用户更方便操作的手机或电脑上完成。

参与角色

  • Device Client:受限设备或 CLI 工具,负责显示验证码并轮询 token。
  • Authorization Server:OAuth 授权服务器,负责生成验证码、处理登录授权、发放 token。
  • User:用户本人,用手机或电脑完成授权。
  • Resource Server:真正提供 API 的服务,例如 GitHub API、Google Drive API。

完整流程

1. 设备申请 device code

设备先向授权服务器请求一组用于设备授权的码:

POST /oauth/device/code
Content-Type: application/x-www-form-urlencoded

client_id=abc123&scope=repo read:user

这里通常包含:

  • client_id:客户端标识;
  • scope:希望申请的权限范围。

Device Flow 的客户端通常是 public client,不能安全保存 client_secret

2. 授权服务器返回验证码信息

授权服务器返回类似结果:

{
  "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": 900,
  "interval": 5
}

字段含义:

字段 作用
device_code 给设备使用的长码,后续轮询 token 时提交
user_code 给用户看的短码,用户在网页上输入
verification_uri 用户需要打开的授权页面
verification_uri_complete 已带上 user code 的完整授权链接,用户打开后可能不必手动输入
expires_in 这组码的有效期,单位通常是秒
interval 设备轮询 token 接口的最小间隔

3. 设备提示用户授权

设备或 CLI 显示:

请打开下面的链接完成授权:
https://example.com/device

然后输入验证码:
WDJB-MJHT

如果有 verification_uri_complete,也可以直接显示完整链接:

https://example.com/device?user_code=WDJB-MJHT

4. 用户在浏览器里登录并确认

用户用手机或电脑打开授权页面,输入验证码,登录账号,并确认是否允许这个应用访问指定权限。

授权服务器会更新这次设备授权请求的状态:已授权、被拒绝,或者已过期。

5. 设备轮询 token 接口

设备从拿到 device_code 开始,按 interval 指定的间隔请求 token 接口:

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

client_id=abc123&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS&grant_type=urn:ietf:params:oauth:grant-type:device_code

关键参数是:

grant_type=urn:ietf:params:oauth:grant-type:device_code

它表示客户端正在用 Device Flow 的授权结果换取 token。

6. 授权成功后返回 access token

用户完成授权后,设备下一次轮询会收到:

{
  "access_token": "access_token_here",
  "token_type": "Bearer",
  "scope": "repo read:user",
  "expires_in": 3600,
  "refresh_token": "refresh_token_here"
}

之后设备就可以带着 access token 调用资源服务器:

GET /user
Authorization: Bearer access_token_here

流程图

设备 / CLI                    授权服务器                    用户浏览器
   |                              |                              |
   | 申请 device_code             |                              |
   |----------------------------->|                              |
   | 返回 device_code/user_code    |                              |
   |<-----------------------------|                              |
   | 显示 URL 和 user_code          |                              |
   |                              | 用户打开页面、输入 code、登录  |
   |                              |<----------------------------->|
   | 轮询 token 接口                |                              |
   |----------------------------->|                              |
   | authorization_pending         |                              |
   |<-----------------------------|                              |
   | 继续轮询                      |                              |
   |----------------------------->|                              |
   | 返回 access_token             |                              |
   |<-----------------------------|                              |

轮询时的几种状态

设备轮询 token 接口时,授权不一定已经完成。常见返回状态如下:

错误码 含义 客户端应该怎么做
authorization_pending 用户还没有完成授权 继续等待,按 interval 轮询
slow_down 轮询太频繁 增加轮询间隔
access_denied 用户拒绝授权 停止流程,提示授权被拒绝
expired_token device code 已过期 停止流程,让用户重新发起登录
invalid_client client_id 不合法 检查客户端配置
invalid_grant device_code 无效或状态异常 停止流程,重新开始

客户端不应该把 authorization_pending 当成失败。它只是表示用户还没完成网页端授权。

为什么 Device Flow 适合 CLI、电视和 IoT 设备

Device Flow 的优势在于:

  • 设备不需要打开浏览器;
  • 设备不需要接收 redirect URI 回调;
  • 用户不用在设备上输入密码和 MFA;
  • 登录和授权发生在授权服务器的正规网页上;
  • CLI 或设备只需要显示短码,并在后台轮询。

所以它尤其适合输入能力弱、没有浏览器、没有公网回调地址的场景。

安全要点

Device Flow 的安全性依赖几个关键设计:

  1. 用户密码不进入设备
    用户只在授权服务器网页上登录,设备只拿到 token。

  2. user code 短但有效期也短
    短码方便输入,但也更容易被猜,所以必须设置过期时间、错误次数限制和风控。

  3. 轮询必须限速
    客户端要遵守 interval,遇到 slow_down 要放慢请求,避免压垮授权服务器。

  4. 授权页面必须展示应用和权限
    用户需要清楚看到哪个应用在请求哪些权限,避免误授权。

  5. 适合 public client
    电视、CLI、IoT 设备通常不能安全保存密钥,因此 Device Flow 不依赖客户端安全保存 client_secret

小结

Device Flow 可以压缩成 6 步:

  1. 设备向授权服务器申请 device_code
  2. 授权服务器返回 device_codeuser_code 和授权 URL;
  3. 设备显示 URL 和 user_code
  4. 用户在另一台设备上打开网页,登录并授权;
  5. 设备按间隔轮询 token 接口;
  6. 授权完成后,设备拿到 access token 并调用 API。

一句话总结:

Device Flow 是 OAuth 为受限设备设计的授权方式:设备显示短码,用户在浏览器里授权,设备轮询直到拿到 token。