学习笔记 · Obsidian

Smith API:OAuth2、OIDC、PKCE 与 Device Flow

LangChainLangSmith

LangSmith 同时提供 OAuth 2.0 authorization code + PKCE、RFC 8628 device flow、refresh token、RFC 7591 dynamic registration、RFC 7009 revocation 和 OIDC discovery/userinfo。Client 管理是 organization-scoped;授权 consent 还必须核对用户可访问的 organization/workspace。

Discovery

端点公开性返回
GET /.well-known/oauth-authorization-serverOpenAPI 标有可选认证方案issuer、authorize/token/device/register/revoke/JWKS、grant/response/scope、PKCE methods、resource support
GET /.well-known/openid-configurationpublicOIDC endpoints、claims、signing algorithms、subject types
GET /oauth/client/{clientID}publicname/logo/homepage/terms/privacy 等非敏感 consent 展示字段
GET /userinfoBearersub、email/verified、name/picture、LangSmith org ID/name

OIDC client 必须验证 issuer、audience、signature、expiry、nonce/state,不因为 discovery 来自官方就跳过 token validation。Public client metadata 不能返回 secret、内部 redirect 或组织私密配置。

Authorization Code + PKCE

sequenceDiagram
    participant C as Client
    participant B as Browser/User
    participant L as LangSmith OAuth
    C->>L: GET /oauth/authorize + client_id + redirect_uri + challenge S256 + state
    L-->>B: 302 consent page
    B->>L: POST /oauth/authorize/approve + org/workspace + challenge
    L-->>B: redirect_uri + authorization code + state
    C->>L: POST /oauth/token + code + verifier + redirect_uri
    L-->>C: access + refresh + id token + workspace_id

/oauth/authorize 要求 response_type=code、registered redirect、code_challenge_method=S256。Approve 是 authenticated multipart request;organization 必须等于当前身份,workspace 可选但必须属于 organization 且用户可访问。state 防 CSRF,client 必须生成、保存并恒定时间比较。

Token endpoint 按 grant_type 分派:authorization_code 需要 code/verifier/redirect;device flow 需要 device_code;refresh 需要 refresh_token。成功响应含 access_token、expires_in、可选 ID/refresh token、token type 与 workspace ID。Authorization code、PKCE verifier、refresh token 都视为 secret,单次/短期使用。

Device Authorization Flow

  1. Client POST /oauth/device/code,只传 client ID,获得 device code、user code、verification URI、expiry、poll interval。
  2. 用户在已登录页面输入 user code;页面 POST /oauth/device/authorize,同时指定匹配当前身份的 organization 与可选 workspace。
  3. Device 按服务器 interval 轮询 /oauth/token 的 device grant,直到 authorized、expired 或 denied。

不得缩短服务器 interval 强轮询;device/user code 要足够随机、短期、一次性、限速并防撞库。CLI/无头设备应把 verification URI 和 user code 清晰展示,但不能把 device code 写到共享日志。

Client 管理

Organization owner 可在 /api/v1/platform/oauth/clients 创建、列表、读取、Patch、删除,或 /{id}/rotate-secret:

  • client type 为 public/confidential;confidential create/rotate 只显示一次 secret;
  • 配置 name、redirect URIs、allowed scopes、grant types、logo/homepage/policy/TOS;
  • Patch 可 disable client;rotate 立即使旧 secret 失效;delete 成功 204。

生产轮换要在支持双 secret 的前提下无损切换;该接口的语义是立即 invalidating previous secret,若没有重叠窗口,必须协调停机/瞬时部署。Metadata URI 仅允许可信 HTTPS,前端展示外部 logo/link 时要防追踪与内容注入。

Dynamic Client Registration

POST /oauth/register 实现 RFC 7591,公开注册只签发 public client;redirect 仅允许受控 loopback、HTTPS 或 native URI,body 上限 8 KiB,成功 201。Request 可含 redirect URIs、name、grant/response、scope、token auth method 等。

Dynamic registration 是攻击面:限制速率/总量、严查 scheme/host/port、拒绝 credential-in-URL 与通配 redirect,不允许借 logo/policy URI SSRF。官方变更记录还说明 headless HTTP redirect 只接受 127.0.0.1 或 [::1],不接受 localhost;客户端应优先读实时 metadata/错误,而不是硬编码旧规则。

Authorized apps 与撤销

  • GET /api/v1/platform/oauth/authorized-apps 列当前用户授权的 app、scopes 与授权时间;
  • DELETE .../{clientID} 撤销该 app 并使用户在此 app 下的 active tokens 失效;
  • POST /oauth/revoke 接受 access/refresh token,按 RFC 7009 无论 token 是否存在都返回 200,防止 token 探测。

撤销后的 resource server 仍需实时/短缓存验证,不能接受到 JWT 过期才生效的长窗口。Client 对 revoke 200 只能认定“请求已被接受为幂等处理”,不能据此证明 token 之前存在。

安全与错误处理

  • Public client 永远不把 client secret 当保护手段,必须 PKCE;confidential client 把 secret 放服务器 secret store。
  • Redirect URI 做 exact match;state、nonce、PKCE、issuer/audience 全部验证。
  • Scope 默认最小;组织/workspace 选择只能从 authenticated identity 的授权集合中取。
  • Token 不进 URL、浏览器持久存储、trace 或 error detail;refresh token rotation/reuse detection 由产品实时能力决定,上线前验证。
  • 400 是 OAuth request/token error;401 未认证;403 当前用户/组织无权;404 隐藏或不存在;500 不应自动重放 authorization code。
  • Client secret create/rotate 响应丢失时不能通过 GET 取回,只能再次受控 rotate。