# 使用 URnetwork 登录

让人们把你的应用或智能体接纳进他们的私有网络，使它能与他们在那里运行的其他一切协同工作。

![使用 URnetwork 登录（Sign in with URnetwork）按钮的两种变体：浅色背景上的深色按钮，深色背景上的浅色按钮](/docs-assets/sign-in-with-urnetwork-buttons.svg)

[下载按钮套件](/docs-assets/sign-in-with-urnetwork-kit.zip) — 标记代码、样式、两种变体各自的单文件资产，以及单独的图形标志。套件已经定稿，你今天就可以采用，先于本页介绍的其他一切。

> **开发中 — 尚未上线。**本页描述的是一个仍在建设中的设计。授权服务器已经写好，但尚未部署：`auth.bringyour.com` 目前还无法解析，下面的端点今天一个也调用不了。`client_id` 按申请签发，不能自助获取。凡标注为*计划中*的内容都尚未构建。按钮套件是真实且已定稿的，你现在就可以采用。

## 它是什么

登录就是接纳这一步。它授予的是*网络上的一个位置* — 可寻址，可被此人的其他软件发现，并且此人随时可以撤销。

这让它与外观相似的那些登录按钮有本质的不同。你主要不是在了解某人是谁。你是在被允许进入某个地方。

这项授权分为两半，分别同意、分别撤销：

- **身份** — 某个 URnetwork 账户批准了你，仅此而已。
- **网络** — 你可以对哪个网络行事：它在同意页面上具名显示，并在令牌签发时绑定进令牌。

应用可以只请求身份。登录本身从不意味着获得网络访问权限。

## 按钮

在浅色和中间色调的背景上使用深色按钮，在深色背景上使用浅色按钮。没有托管脚本：套件就是 HTML、CSS 和一个内联 SVG，所以把按钮放到你的页面上之后，在有人点击它之前不会传输任何东西。

只要条件允许，就优先使用下面的标记代码，而不是 SVG 资产 — 这样标签会以你页面自己的字体渲染，并保持可选中、可搜索、可翻译。[套件](/docs-assets/sign-in-with-urnetwork-kit.zip)里的单文件 SVG 用于那些只接受图片、别无其他的地方。

按钮上的文字是 **Sign in with**，后面接着 URnetwork 标识组合（lockup）。只有前一部分是文字，这正是按钮能干净利落地翻译的原因：用你的界面语言渲染"Sign in with"，标识组合保持原样，这样品牌在任何地方读起来都一样。

```html
<a class="ur-signin" href="YOUR_AUTHORIZE_URL" aria-label="Sign in with URnetwork">
  <span>Sign in with</span>
  <svg class="ur-signin-lockup" viewBox="0 0 980 139" fill="currentColor" aria-hidden="true">
    <!-- the lockup, 10 paths — copy them from the kit -->
  </svg>
</a>
```

标识组合用 `currentColor` 绘制，因此它继承按钮的文字颜色，一份副本就能同时用于两种变体。`aria-label` 承载完整的名称，因为名称有一半是图形。

```css
.ur-signin {
  display: inline-flex; align-items: center; gap: 10px;
  height: 44px; padding: 0 20px; min-width: 200px;
  border-radius: 12px; border: 1px solid transparent;
  background: #101010; color: #f8f8f8;
  font: 500 15px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  text-decoration: none; white-space: nowrap; cursor: pointer;
}
.ur-signin:hover         { background: #1c1c1c; }
.ur-signin:focus-visible { outline: 2px solid #0099ff; outline-offset: 2px; }
.ur-signin-lockup        { height: 16px; width: auto; flex: none; }

/* Light variant, for dark surfaces */
.ur-signin.is-light       { background: #f8f8f8; color: #101010; border-color: rgba(16,16,16,.14); }
.ur-signin.is-light:hover { background: #ececec; }
```

标签使用系统字体栈，而不是 URnetwork 的品牌字体，因为后者不能再分发到其他网站 — 而钉住一个 Web 字体，又会让按钮重新带上一次网络请求。

### 品牌规则

| | |
| --- | --- |
| 高度 | 最小 44px — 这同时也是点按目标。绝不能更小。 |
| 圆角半径 | 12px |
| 标识组合 | 16px 高，`currentColor`，绝不重绘、改色或拆开重组 |
| 留白 | 四边各 8px |
| 焦点环 | 2px `#0099ff`，偏移 2px |
| 底色 | 深色 `#101010`，浅色 `#f8f8f8`。不用其他颜色，不用渐变。 |

不要用你自己的字体排出 URnetwork 这个词来代替标识组合，不要在没有标签的情况下使用图形标志，也不要给按钮加动画或让它倾斜变形。把"Sign in with"翻译成你的界面语言；标识组合永不改变。

## 工作原理

*计划中。下面的端点尚未部署。*

只用带 PKCE 的授权码流程，别无其他 — 这是 OAuth 2.1，所以没有隐式授权，`S256` 是强制要求。

| | |
| --- | --- |
| 签发者 | `https://auth.bringyour.com` |
| 发现 | `/.well-known/openid-configuration` 和 `/.well-known/oauth-authorization-server` |
| 密钥 | `/.well-known/jwks.json` |
| 授权 | `https://ur.io/authorize` |
| 令牌 | `POST /oauth/token` |
| UserInfo | `GET /oauth/userinfo` |
| 撤销 | `POST /oauth/revoke` |

授权端点有意放在与签发者不同的源上。同意页面必须复用已登录的会话，而浏览器存储以源为作用域，所以这个页面就放在会话本来所在的地方。RFC 8414 允许这样做，签发者身份也不受影响 — 继续对照 `auth.bringyour.com` 校验 `iss`。

你第一次请求某一组作用域时，总会显示同意页面，即使此人已经登录。悄无声息地批准第三方，会让这个页面沦为混淆代理人（confused deputy）。

## 加入网络

*计划中。*

OAuth 访问令牌只负责授权；它不会说 `connect` 协议。要占据授予你的那个网络位置，就去交换它：

```
POST /network/auth-client
Authorization: Bearer <access token>
```

你会收到一个携带 `clientId` 的平台 JWT — 协议正是凭这个地址认出你。从那以后，你就是那个网络的一名普通成员，要发现还有什么被接纳了进来，就用 `/network/peers`。

有三件事常让人意外：

1. 这是两套凭证体系之间唯一的跨越，而且它是一个在服务器端做出的授权决定，而不是一个能在两处验证通过的令牌。签名密钥集始终互不相交。
2. 开通一个客户端**会向该网络计费**。同意页面上写明了这一点；这里再说一遍，免得有人从账单上才知道。
3. 这份凭证的作用范围是授权时绑定的那一个网络。它永远不会跟随某人去到另一个网络。

当两个应用需要知道它们服务的是同一个人时，共享的标识是它们都被接纳进的那个网络 — 而不是那个人。这是有意为之：网络是经过同意的、在屏幕上具名显示的、可撤销的，而一个持久的用户标识符这几点一样也不具备。

## 令牌

*计划中。*

| 令牌 | 有效期 | 说明 |
| --- | --- | --- |
| 访问令牌 | 1 小时 | 受众绑定到单一资源 |
| 刷新令牌 | 90 天，滑动续期 | 不透明且轮换 — 每次使用都会让上一个作废 |
| ID 令牌 | 1 小时 | 受众是你的 `client_id`；绝不要把它发送给资源服务器 |

刷新令牌会轮换，重用一个已作废的刷新令牌会被视为盗用：整个令牌族都会被撤销。保存你最新拿到的那一个。

## 对你的应用的要求

- **使用 `S256` 的 PKCE。**不可省略。
- **精确匹配的重定向 URI**，须预先注册，仅限 HTTPS。原生应用可以使用 `http://127.0.0.1:<port>/…`。
- **校验授权响应上的 `iss`** — 防御混淆攻击（mix-up attack）靠的正是这一步。
- **校验 ID 令牌上的 `aud`** 是否等于你的 `client_id`。
- **不要把"使用 URnetwork 登录"令牌出示给运营方 API。**两套密钥集互不相交；这不可能奏效，而这是设计本意，不是疏漏。

## 获取 `client_id`

签发需经审核，并不开放。被接纳的应用能够寻址某人的设备，所以客户端在能够请求身份或网络访问之前，要先经过审查。对仅限 MCP 的作用域，动态注册仍然可用，这类作用域能触及的只是一次提供者查询。

目前还没有自助控制台。向我们申请 `client_id`，我们会为你签发一个。

## 撤销

*计划中。*

人们在 URnetwork 应用的已连接应用（connected apps）界面中管理授权，撤销其中一项会移除它所开通的网络客户端。你的应用观察到的是 connect 会话断开、`clientId` 不再能解析 — 这是一次撤销，不是故障。

访问令牌的有效期为一小时，所以撤销会在一小时之内生效，而不是即时生效。
