---
title: Layer 1 — 认证规则
description: 配置应用接受哪些认证方式，并进一步限制单次认证请求中可用的方式。
editUrl: true
head: []
template: doc
sidebar:
  order: 2
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

import { CardGrid, LinkButton, LinkCard } from "@astrojs/starlight/components";

:::tip[三层规则模型的一部分]
认证规则（Layer 1）是三层规则之一。概念页介绍了允许列表、默认拒绝、评估顺序，以及三层规则如何组合。

<LinkButton href="/zh-cn/application-rules/overview/" variant="secondary" icon="left-arrow">阅读概述</LinkButton>
:::

认证规则控制**应用允许使用哪些认证方式**。系统会在向用户展示可选方式时（`/reason/email`）以及每次实际认证尝试中检查这些规则。

## 支持的方式

| 方式 | Payload | 说明 |
|---|---|---|
| `PASSKEY_USERNAMELESS` | `{}` | WebAuthn / FIDO2 passkey 的 **discoverable-credential / 无用户名（usernameless）** 流程。它 gate 在任何邮箱输入**之前**显示的那个独立「使用 passkey 登录」按钮。两种 passkey 方法登录时用的是用户注册的同一个 passkey —— 区别只在于由哪个按钮唤起。详见下文的 [无用户名 passkey](#无用户名-passkey)。 |
| `PASSKEY_REASONED` | `{}` | WebAuthn / FIDO2 passkey 的**邮箱优先**流程。它 gate 在用户输入邮箱**之后**提供的 passkey 选项。 |
| `EMAIL_VERIFICATION` | `{}` | 向用户邮箱发送一次性验证码。 |
| `STEAM_TICKET` | `{ "allowedSteamAppIds": number[] }` | 在 Steam 发行的游戏客户端内由 Steamworks SDK 发起的一次性 Steam ticket 交换，由 `native-api POST /direct-issue/steam-ticket` 消费。`allowedSteamAppIds` 把规则收紧到具体的 Steam App ID 列表。端到端用法见 [原生客户端](/zh-cn/native/overview/)。 |
| `STEAM_OPENID` | `{}` | 浏览器端的 "Sign in with Steam" 按钮，使用 Steam 的 OpenID 2.0 OP。此处解析得到的 Steam 身份与 `STEAM_TICKET` **共用同一条用户身份行**，所以一个用户从游戏端首次登录后可以接着从网页按钮登录（反之亦然）。这条路径不需要 client ID、client secret、Web API key —— 通过 `openid.mode=check_authentication` 做无密钥验证。 |
| `ACCESS_KEY_DIRECT` | `{}` | 一次性的 AccessKey 凭据登录，由 `native-api POST /direct-issue/access-key` 消费。凭据格式与端到端流程见 [原生客户端](/zh-cn/native/overview/)。 |
| `GOOGLE_OAUTH` | `{}` | 通过 Google 作为上游 OIDC 提供方登录。Sudomimus 在此扮演 OIDC 接入方角色。Phase 1 的 payload 为空；未来阶段会加入 `allowedHostedDomains` 以支持 Google Workspace 域名限制。 |
| `GITHUB_OAUTH` | `{ "allowedGitHubOrgs": string[] }` | 通过 GitHub 作为上游 OAuth 2.0 提供方登录（没有 id_token；profile 与已验证邮箱列表通过 GitHub REST API 拉取）。`allowedGitHubOrgs` 是 GitHub Organization `login` 的精确匹配列表（大小写不敏感）；空数组表示不限制组织，非空表示用户必须属于其中至少一个组织。只有匹配规则包含非空允许列表时，才会请求 `read:org` scope。未配置组织限制时，授权页与 Phase 2 相同，只请求 `read:user user:email`。 |
| `DISCORD_OAUTH` | `{}` | 通过 Discord 作为上游 OAuth 2.0 提供方登录（没有 id_token；profile 与邮箱通过 `GET /users/@me` 拉取）。Scopes 为 `identify email`。一封邮箱只有当 Discord 同时返回非空 `email` 与 `verified: true` 时才被视为已验证；否则账户会被创建但**不会**记录已验证邮箱，因此 Layer 2 `EMAIL` 规则会 fail-closed（这是有意为之，与 Google / GitHub 一致）。未来阶段将加入 `allowedDiscordGuilds` 用于服务器（guild）限制。 |
| `BATTLENET_OAUTH` | `{}` | 通过 Battle.net 作为上游 OIDC-shaped OAuth 提供方登录。Battle.net 的 `/userinfo` 只返回 subject 和 BattleTag —— **没有邮箱** —— 因此账户会被创建但不记录已验证邮箱，Layer 2 `EMAIL` 规则对 Battle.net-only 账户 fail-closed（有意为之）。Battle.net 没有 per-application 限制概念，payload 恒为空。 |
| `X_OAUTH` | `{}` | 通过 X（原 Twitter）作为上游 OAuth 2.0 提供方登录。X 的 v2 `/2/users/me` **不提供邮箱**，因此与 Battle.net、Steam 一样，账户会被创建但不记录已验证邮箱，Layer 2 `EMAIL` 规则 fail-closed（有意为之）。payload 为空；无 per-application 限制。 |
| `ENTERPRISE_FEDERATION_APPLICATION_MANAGED` | `{ "connectorAnchor": string }` | 通过你**自己组织**注册为[外部连接](/zh-cn/domains-federation/federation-connectors/)的 OIDC 身份提供方进行「使用 …… 登录」。`connectorAnchor` 指向一个归该应用所属组织所有的外部连接；一条规则渲染一个按钮。登录走标准的鉴权与 realize 流水线。详见[使用你的 IdP 登录](/zh-cn/domains-federation/sign-in-with-your-idp/)。 |
| `ENTERPRISE_FEDERATION_DOMAIN_MANAGED` | `{}` | 让应用 opt-in，接受**强制 SSO** 登录。payload 为空——外部连接在登录时由用户的邮箱域名解析得出（一个由其所有者设置了 `SSO_ONLY` [登录策略](/zh-cn/domains-federation/domain-login-policy/)的已验证域名），不在规则里指名。没有这条规则的应用会拒绝被 SSO 管控的用户。详见[使用你的 IdP 登录](/zh-cn/domains-federation/sign-in-with-your-idp/)。 |

`STEAM_TICKET` 与 `ACCESS_KEY_DIRECT` 的端到端用法见 [原生客户端](/zh-cn/native/overview/)；两种企业联合方法见 [域名与联合登录](/zh-cn/domains-federation/overview/) 一节。

## 应用规则结构

每条认证规则描述一种认证方式。要允许多种方式，请分别创建多条规则。

```json
{
  "method": "PASSKEY_REASONED",
  "payload": {},
  "accessTokenTtlSeconds": null,
  "refreshTokenTtlSeconds": null
}
```

`STEAM_TICKET` 的 `payload` 包含允许的 App ID 列表：

```json
{
  "method": "STEAM_TICKET",
  "payload": { "allowedSteamAppIds": [480, 730] },
  "accessTokenTtlSeconds": null,
  "refreshTokenTtlSeconds": null
}
```

两个 TTL 字段是可选的；如果提供，它们会参与发放令牌时的 [最小值 fold](/zh-cn/application-rules/overview/#token-ttl)。

## 在 `/establish` 上收紧

`/establish` 中的 `authenticationConstraints` 使用相同结构，用于限制单次认证请求：

```json
{
  "applicationAnchor": "my-app",
  "authenticationConstraints": [
    { "method": "PASSKEY_REASONED", "payload": {} }
  ]
}
```

- 字段缺失 → 不收紧；只看应用规则。
- 字段存在且为空数组 → 拒绝。
- 字段存在且非空 → 和应用规则做 AND。

## 示例

某应用配置了两条认证规则：`PASSKEY_REASONED` 与 `EMAIL_VERIFICATION`。管理员入口在一次认证请求中传入 `authenticationConstraints: [{ "method": "PASSKEY_REASONED", "payload": {} }]`。

| 方式 | 应用允许？ | Inquiry 允许？ | 结果 |
|---|---|---|---|
| `PASSKEY_REASONED` | 是 | 是 | 提供 |
| `EMAIL_VERIFICATION` | 是 | 否 | 隐藏 |

用户在这次会话里只看得到 passkey 选项，尽管应用本身也是允许邮箱方式的。

## 无用户名 passkey

passkey 登录拆分成**两个相互独立**的 Layer-1 方法，而不是一条带 flag 的规则：

- `PASSKEY_REASONED` 是**邮箱优先**流程：用户先输入邮箱，`/reason/email` 解析出其账户，然后用注册过的凭据完成挑战。
- `PASSKEY_USERNAMELESS` 是 **discoverable-credential** 流程：在认证 UI 顶部、邮箱框之前，显示一个独立的「使用 passkey 登录」按钮。用户点一下，浏览器弹出原生 passkey 选择器，挑一个凭据并完成验证（指纹 / 面部 / PIN），整个过程无需输入邮箱。

「仅无用户名」的应用 —— 即**没有**任何邮箱框提供 passkey 选项 —— 只需仅允许 `PASSKEY_USERNAMELESS` 即可表达：

```json
{
  "method": "PASSKEY_USERNAMELESS",
  "payload": {}
}
```

要点：

- 这两个方法是独立的规则，payload 均为空。要邮箱优先入口就配 `PASSKEY_REASONED`，要独立按钮就配 `PASSKEY_USERNAMELESS`，两者都要就都配。已不再有 `allowUsernameless` 这个 flag。
- 两个方法都解析到同一条共享的 `PASSKEY` 凭据行，因此通过其中一个流程注册的凭据可被另一个流程使用。
- 每次 inquiry 的 `authenticationConstraints` 可以携带 `PASSKEY_USERNAMELESS`，把单次 inquiry 收紧到只剩 discoverable-credential 按钮（与应用规则做 AND，和其它方法一样）。
- Discoverable-credential 流程要求 authenticator 设置 User Verified (UV) 标志（指纹 / PIN）。没有用户验证的无用户名登录会被拒绝 —— 因为没有输入的邮箱可以作为第二因素。
- 这两条规则不影响 passkey 的**注册**流程：用户仍然通过常规的"邮箱验证后注册 passkey"路径来添加凭据。`PASSKEY_USERNAMELESS` 只控制**登录**时是否提供这个独立按钮。

## 相关链接

<CardGrid>
<LinkCard
    title="三层规则模型"
    description="整体模型：允许列表、默认拒绝、评估顺序，以及三层如何组合。"
    href="/zh-cn/application-rules/overview/"
/>
<LinkCard
    title="Layer 2 —— 身份准入规则"
    description="认证后的身份准入检查，可按邮箱等身份信息建立允许列表。"
    href="/zh-cn/application-rules/realize-rules/"
/>
<LinkCard
    title="Layer 3 —— 返回规则"
    description="把 realize 后的会话如何回传给应用。"
    href="/zh-cn/application-rules/return-rules/"
/>
</CardGrid>