Authagonal

文档

开始使用 Authagonal 所需的一切——从创建您的第一个租户到接入 SSO、SCIM 和自定义品牌。

快速入门

Authagonal 为每个租户提供完全符合标准的 OIDC 服务器。每个租户拥有自己的发行者 URL、发现文档和令牌端点——租户之间不共享基础设施。您可以在 5 分钟内从零开始完成登录流程的搭建。

创建租户

在 authagonal.io 注册并为您的租户选择一个 slug。该 slug 将成为您的发行者域名:{slug}.authagonal.io。创建账户后,验证您的电子邮箱以激活租户。

Authagonal signup page showing tenant slug input and email verification

在注册时为您的租户选择唯一的 slug

注册客户端

在门户侧边栏中导航到客户端,然后点击新建客户端。为您的应用输入 clientId 和 clientName。然后在客户端的URIs选项卡中添加至少一个重定向 URI;这是用户完成身份验证后跳转的地址。例如:https://app.example.com/callback。新客户端默认要求客户端密钥,因此对于没有后端的浏览器应用,请在常规选项卡中关闭要求密钥。

New client form with Client ID and Client Name fields

在门户中注册新的 OAuth 客户端

本地开发

本地开发时使用 http://localhost:3000/callback 作为重定向 URI。Authagonal 允许 localhost 来源使用非 HTTPS 重定向 URI。

您的首次登录

最快的集成方式是使用 oidc-client-ts,一个轻量级的 JavaScript 和 TypeScript 应用 OIDC 客户端库。

oidc-client-ts integration
import { UserManager } from 'oidc-client-ts';

const mgr = new UserManager({
  authority: 'https://acme.authagonal.io',
  client_id: 'my-app',
  redirect_uri: 'https://app.example.com/callback',
  response_type: 'code',
  scope: 'openid profile email',
});

// Redirect to login
mgr.signinRedirect();

// On callback page
const user = await mgr.signinRedirectCallback();
console.log(user.profile); // { sub, email, name, ... }

如果您更喜欢不使用库的最简方式,可以使用标准的 OAuth 2.0 授权码流程配合原生 fetch:

Minimal fetch-based flow
// 1. Redirect the user to the authorization endpoint
const authorizeUrl = new URL('https://acme.authagonal.io/connect/authorize');
authorizeUrl.searchParams.set('client_id', 'my-app');
authorizeUrl.searchParams.set('redirect_uri', 'https://app.example.com/callback');
authorizeUrl.searchParams.set('response_type', 'code');
authorizeUrl.searchParams.set('scope', 'openid profile email');
authorizeUrl.searchParams.set('code_challenge', codeChallenge);
authorizeUrl.searchParams.set('code_challenge_method', 'S256');
window.location.href = authorizeUrl.toString();

// 2. On the callback page, exchange the code for tokens
const res = await fetch('https://acme.authagonal.io/connect/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: new URLSearchParams(window.location.search).get('code')!,
    redirect_uri: 'https://app.example.com/callback',
    client_id: 'my-app',
    code_verifier: codeVerifier,
  }),
});

const tokens = await res.json();
// tokens.id_token, tokens.access_token (refresh_token only when offline_access is requested and allowed)
Authagonal login page with email and password fields, branded with tenant logo

您的租户默认登录页面

沙箱模式

先在沙箱环境中测试您的集成。在门户的环境下创建一个:每个沙箱都有自己的 URL({env}-{slug}.authagonal.io,例如 test1-acme.authagonal.io),并且可以随时重置为空,而不影响正式用户。

仪表板

门户仪表板为您提供租户的实时概览。它展示最重要的指标——用户增长、身份验证活动,以及快速导航到门户中的每个功能。

概览

仪表板顶部显示欢迎消息,以及一个指向租户托管登录页面的打开登录页面链接。统计卡片下方的月活跃用户用量表会对照您的方案限额跟踪用量,一旦超过 80%,便会提供指向方案选项的链接。登录活动图表和列出最新审计条目的最近活动动态构成了页面的其余部分。

Full dashboard view showing the welcome banner, stat cards, monthly active users meter, and sign-in activity chart

仪表板主屏幕,包含统计卡片和登录活动

活动指标

六张统计卡片让您一眼掌握租户概况:

  • 活跃用户:租户中的用户总数
  • 登录 (24h):过去 24 小时内的成功登录次数
  • 已启用 MFA:已注册 MFA 的用户百分比,附带已注册人数和总人数
  • 失败尝试 (24h):过去 24 小时内失败的登录,例如凭证错误、账户锁定或策略拒绝
  • SCIM 同步:来自已连接身份提供商的配置活动,显示为“空闲”或操作次数
  • 月度支出:本月迄今的支出,附带预计的月末金额

24 小时卡片会将过去 24 小时与此前的 24 小时进行比较。登录活动图表按天绘制 14、30 或 90 天内的成功和失败登录次数。仪表板每分钟刷新一次。

Six stat cards showing active users, sign-ins, MFA enrolment, failed attempts, SCIM sync, and monthly spend

汇总过去 24 小时情况的统计卡片

快速导航

指标下方,导航卡片直接链接到客户端、SSO、用户、SCIM、品牌设置、设置、账单、域名和审计日志。每张卡片都显示简要说明,方便新团队成员快速了解。

客户端

OAuth 客户端代表通过您的租户进行用户身份验证的应用。每个客户端都有自己的重定向 URI、作用域、授权类型、令牌有效期和 MFA 策略配置。

客户端列表

客户端页面显示所有已注册客户端的表格。每行显示 clientId、显示名称、以彩色标签形式显示的允许授权类型,以及是否启用了 PKCE。点击任意行可打开完整的配置编辑器。

Clients table showing clientId, name, grant type badges, and PKCE status for each registered application

带有授权类型标签和 PKCE 指示器的客户端列表

创建客户端

点击新建客户端以注册新应用。您需要提供两个字段:

  • clientId——客户端的唯一标识符(例如 my-spa)
  • clientName——易于阅读的显示名称
New client form with Client ID and Client Name input fields

注册新的 OAuth 客户端

删除客户端

要删除客户端,请点击客户端表格中该行的垃圾桶图标,并输入客户端 ID 进行确认。客户端会被永久删除,此后无法再让用户登录或刷新令牌。已签发的访问令牌不会被撤销,在过期前仍然有效。

客户端配置参考

每个客户端都有一组全面的配置选项,分为五个选项卡:常规、URIs、权限范围、令牌和安全。

常规设置

设置项描述默认值
clientName在同意屏幕和门户中显示的名称–
requirePkce在授权码流程中要求 Proof Key for Code Exchange开启
requireClientSecret令牌请求时要求客户端密钥(对公共客户端如 SPA 禁用此项)开启
allowOfflineAccess允许客户端通过 offline_access 作用域请求刷新令牌关闭
alwaysIncludeUserClaimsInIdToken即使未请求相应的作用域,也在 ID 令牌中包含个人资料、电子邮件、角色和组声明关闭
includeGroupsInTokens在请求 groups 作用域时签发的令牌中,以 groups 声明的形式包含用户所属 SCIM 组的名称关闭

PKCE 安全性

禁用 PKCE 会降低授权码流程的安全性。仅对不支持 PKCE 的旧版客户端禁用此项。所有现代应用都应保持 PKCE 启用。

URI

URI 字段使用标签输入——输入值后按回车或逗号添加。点击标签上的 X 可将其移除。

设置项描述
redirectUris身份验证后允许的回调 URL。必须与授权请求中的 redirect_uri 参数完全匹配。
postLogoutRedirectUris注销后允许重定向到的 URL。
allowedCorsOrigins允许对令牌和 UserInfo 端点进行跨域请求的源。
URI configuration section showing tag inputs for redirect URIs, post-logout URIs, and CORS origins

用于配置 URI 的标签输入字段

作用域与授权类型

设置项选项
allowedScopesopenid profile email offline_access phone roles groups
allowedGrantTypesauthorization_code client_credentials refresh_token urn:ietf:params:oauth:grant-type:device_code urn:ietf:params:oauth:grant-type:token-exchange

令牌有效期

设置项描述默认值
accessTokenLifetimeSeconds访问令牌的有效时长1800(30 分钟)
identityTokenLifetimeSecondsID 令牌的有效时长300(5 分钟)
authorizationCodeLifetimeSeconds授权码可用于交换的有效时长300(5 分钟)
absoluteRefreshTokenLifetimeSeconds刷新令牌的最大有效期,不受活动状态影响2592000(30 天)
slidingRefreshTokenLifetimeSeconds刷新令牌在每次使用时重置过期时间,但不超过绝对有效期1296000(15 天)
Token lifetime configuration fields with numeric inputs for each lifetime setting

按客户端配置令牌有效期

登出 URI

客户端可以注册 back-channel 和 front-channel 登出 URI。两者均为可选——配置与您的应用清理会话方式相匹配的那一个或两个。

设置项描述
backChannelLogoutUri带签名登出令牌的服务器到服务器 POST。即使用户浏览器离线也可靠。
frontChannelLogoutUri在登出时加载到隐藏 iframe 中,使浏览器清理 cookie 和本地存储。
frontChannelLogoutSessionRequired启用时,登出 URL 会收到 iss 和 sid 查询参数,以便应用将登出与特定会话关联。

一起使用

Back-channel 保证服务器收到通知;front-channel 清理浏览器。大多数应用都会受益于同时配置两者。

MFA 策略

每个客户端都有自己的 MFA 策略,在客户端的<strong>安全</strong>选项卡中设置。MFA 策略下拉菜单提供三个选项:

策略行为
已禁用此客户端不会提示 MFA
已启用用户可以选择注册 MFA;已注册的用户在登录时会收到提示
必需所有用户必须完成 MFA 才能通过此客户端进行身份验证
MFA policy dropdown showing Disabled, Enabled, and Required options on the client configuration page

按客户端的 MFA 策略

企业 SSO

企业 SSO 让您的客户使用自己的身份提供商。Authagonal 支持 SAML 2.0 和 OIDC 联合身份验证,配合基于域的路由,用户可以根据其电子邮箱地址自动跳转到正确的身份提供商。

SSO domain routing flow: shows how email domains are matched to SAML or OIDC identity providers

基于域的 SSO 路由

SAML 2.0 连接

要创建 SAML 连接,请导航到 SSO 页面并选择 SAML 选项卡。提供以下信息:

字段描述
connectionName此连接的可读名称(例如「Acme Corp Okta」)
entityId您的 SP 实体 ID。请在您的 IdP 中将此值原样注册为应用程序的标识符(Entity ID);断言必须将其作为 Audience
metadataLocationIdP 的 SAML 元数据 XML 文档的 URL
metadataXml粘贴的 IdP 元数据 XML,适用于没有元数据 URL 的 IdP(Google Workspace)或其 URL 无法从互联网访问的情况。此项与 metadataLocation 二选一,不能同时提供
nameIdFormat可选的、向 IdP 请求的 NameID 格式。留空使用默认的 emailAddress,或设为 "none" 以完全省略 NameIDPolicy(ADFS 推荐)
allowedDomains路由到此连接的电子邮箱域名(例如 acme.com)。通过 API 设置;门户会在连接卡片和“域名路由”选项卡上显示它们

保存连接后,Authagonal 会获取元数据文档并导入身份提供商的签名证书、SSO 端点 URL 和名称标识符格式。元数据会定期刷新以获取证书轮换。

SAML connection creation form with fields for connection name, SP entity ID, and IdP metadata

创建 SAML 2.0 SSO 连接

OIDC 连接

要创建 OIDC 联合身份验证连接,请选择 OIDC 选项卡并提供:

字段描述
connectionName此连接的可读名称
metadataLocationOpenID Connect 发现 URL(例如 https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration)
clientId在外部身份提供商中为此联合注册的 client ID
clientSecret外部身份提供商注册的 client secret
allowedDomains路由到此连接的电子邮箱域名(例如 acme.com)。通过 API 设置;门户会在连接卡片和“域名路由”选项卡上显示它们
OIDC connection creation form with fields for connection name, discovery URL, client ID, and client secret

创建 OIDC 联合身份验证连接

域路由

域路由根据用户的电子邮箱域名自动将用户重定向到正确的身份提供商。当用户在登录页面输入电子邮箱时,Authagonal 会检查域名部分(例如 acme.com)是否匹配任一 SSO 连接的 allowedDomains。如果匹配,用户将被无缝重定向到其组织的身份提供商。

电子邮箱域名SSO 提供商协议
acme.comAcme Corp OktaSAML 2.0
contoso.comContoso Azure ADOIDC
example.orgExample OneLoginSAML 2.0
Domain routing table showing email domains mapped to SSO connections with protocol type

域路由将电子邮箱域名映射到身份提供商

SP 发起的流程

SP 发起的流程是默认模式——用户从您的登录页面开始,自动路由到正确的身份提供商。用户也可以通过 /saml/{connectionId}/login 或 /oidc/{connectionId}/login 直接链接到特定连接。

JIT 配置

当用户首次通过 SSO 登录且在您的租户中不存在时,Authagonal 可以自动创建其账户(即时配置)。JIT 配置默认关闭:在创建连接时勾选启用 JIT 开通,即可按连接开启。

禁用 JIT 配置后,只有预先配置的用户——通过 SCIM、门户的用户页面或 API——才能通过该连接登录。未知用户会收到 access_denied 错误,并被引导联系其管理员。

按连接设置

JIT 配置是按 SSO 连接控制的,而非租户范围。您可以让一个连接允许 JIT(例如,用于自行管理用户的合作伙伴组织),而另一个连接要求预先配置(例如,用于使用 SCIM 同步的企业客户)。

推广前测试

在向生产用户推广前,使用沙箱模式测试 SSO 连接。这可以让您在不影响正式身份验证流程的情况下验证身份提供商配置、属性映射和域路由。

组织范围的连接

连接也可以只属于租户内的某一个组织,而不是整个租户。只有在该组织已经被确定之后才会提供这个连接:通过绑定到它的自定义域名、登录链接上的 organization 参数,或注册到该组织的客户端来确定,绝不会出现在租户级的登录页面上。这几种方式的优先级按上述顺序从高到低检查。

登录即创建成员关系

通过组织范围的连接登录的用户会自动成为该组织的成员,与 JIT 配置创建其账户的方式一致,无需单独的邀请步骤。

域名唯一性按范围计算

一个电子邮件域名可以在租户范围内声明一次,并且在每个组织内部各独立声明一次:acme.com 可以路由到租户级连接,在已选定某个组织后,也可以路由到该组织自己的连接。它不能做的是在同一范围内属于两个连接,保存连接时 Authagonal 会拒绝这种情况。

用户

用户页面让您管理租户中的所有终端用户。您可以搜索用户、查看用户详情、邀请新用户,并查看每个用户的配置来源。

搜索栏可精确匹配用户 ID 或电子邮箱,也可按电子邮箱、名字或姓氏的前缀匹配;“全部”、“活跃”和“非活跃”筛选器按状态缩小列表范围。搜索有 300 毫秒的防抖延迟,因此在您输入时结果会更新,而不会过度调用 API。结果以每页 50 个用户分页;使用表格底部的导航控件在页面之间切换。

用户表格

用户表格为每个用户显示以下列:

列描述
用户用户的姓名(未设置姓名时显示电子邮箱),下方显示其电子邮箱,在电子邮箱确认之前带有“未验证”标签
状态Active 或 Inactive ——表示账户是否已启用
来源SCIM 或 Local ——用户的创建方式
角色分配给该用户的角色
MFAEnabled 已注册多因素身份验证时显示,否则显示一条短横线
创建时间用户账户的创建日期
User list table with columns for user, status, source, roles, MFA, and created date

带搜索栏和分页的用户列表

邀请用户

点击邀请用户邀请他人加入您的租户。对方会收到一封电子邮件,用于自行设置密码。表单包含:

字段描述
email用户的电子邮箱地址(在租户内必须唯一)
firstName用户的名字
lastName用户的姓氏
locale首选语言。设置用户的界面和邮件语言;可选,默认回退到英语。
organizationId可选,要将用户添加到的组织,以及该用户在其中的角色。当您的租户有组织时显示
Invite user form with email, first name, last name, and Language fields

邀请新用户

SCIM 配置的用户

通过 SCIM 创建的用户会在来源列中带有「SCIM」标签。其生命周期(创建、更新和停用)由上游身份提供商管理。

首选语言

每个用户都有一个首选语言,它同时决定其托管界面以及 Authagonal 发送给他们的事务性电子邮件(验证、密码重置、欢迎等)。您可以在邀请用户时设置它,并随时从用户详情页面更改。如果用户未设置首选语言,Authagonal 将回退到英语。选择器提供所有受支持的语言:英语、简体中文、德语、法语、西班牙语、越南语、葡萄牙语、日语、阿拉伯语、印地语和南非荷兰语。

User detail edit form with profile fields including a preferred Language selector

在详情页面设置用户的首选语言

用户详情

点击用户列表中的任意一行以打开其详情页。您可以在此编辑个人资料、管理角色、重置 MFA、查看自定义属性,以及删除用户。

User detail page showing profile fields, status, assigned roles, and custom attributes

个人资料

编辑用户的邮箱、姓/名、电话、公司、语言、外部 ID 和激活状态。更改邮箱或邮箱已确认标志需要管理员或所有者权限。邮箱更改必须在租户内保持唯一;如果已被占用,API 会返回 email_already_in_use。

角色

分配和取消在角色页面定义的角色。当客户端请求 roles 作用域时,已分配的角色会作为 roles 声明出现在 ID 和访问令牌中。

多因素认证

查看为用户注册的每个 MFA 凭据,包括身份验证器应用(TOTP)、WebAuthn/通行密钥以及恢复码,每个都有注册和最后使用时间戳。您可以删除单个凭据,或重置所有 MFA;两者都需要管理员或所有者权限。重置会强制用户在下次登录时重新注册。

自定义属性

附加到用户的任意键/值数据。键必须唯一。属性通过用户资料 API 和 SCIM 公开,并可通过配置自定义作用域的 userClaims 映射到访问令牌声明。

组织

该用户所属的组织。它是您自己的一个自由文本标识符,会作为 org_id 声明出现在该用户的令牌上,以及出现在 /connect/userinfo 上(需带 profile 作用域)。Authagonal 从不推导这个值:它由您的配置应用设置,或由创建该用户的 SCIM 凭据设置,也可以在此处手动设置。

字段旁边的链接会列出共享该值的所有人,同一个筛选条件在 API 上也可用,形式为 GET /api/v1/users?organizationId=。用它来查明谁属于某个特定客户,而不必翻遍整个用户目录。

删除用户

永久删除该用户及其所有 MFA 凭据。输入用户邮箱以确认——无法撤销。

组

组让您组织用户并在令牌中包含组成员关系。组可以在门户中手动创建,也可以通过 SCIM 从外部身份提供商自动配置。

组列表

组列在 SCIM 页面的“群组”选项卡中,并显示以下信息:

列描述
组名组的显示名称
成员当前组中的用户数量
来源SCIM 或 Manual ——组的创建方式
创建时间组的创建日期
Groups list table showing group name, member count, source badge, granted roles, and created date

带来源指示器的组列表

创建组

点击新建群组并输入组的显示名称。组名应具有描述性且在租户内唯一(例如「Engineering」、「Billing Admins」、「Beta Testers」)。

组详情与成员

点击任意组可打开详情视图。在这里您可以查看所有当前成员并管理成员关系:

  • 添加成员:按电子邮箱或姓名搜索用户并将其添加到组中。
  • 移除成员:点击任意成员旁边的移除按钮并确认。通过该组授予的所有角色都会被撤销。
Group detail view showing the member list, a user search to add members, and the roles the group grants

在详情视图中管理组成员关系

令牌中的组

当客户端启用了在令牌中包含群组且请求了 groups 作用域时,签发的令牌会包含一个 groups 声明,列出用户所属组的显示名称:

groups claim
{
  "sub": "user-123",
  "email": "[email protected]",
  "groups": ["Engineering", "Beta Testers"]
}

按客户端启用

在令牌中包含群组设置在每个客户端上单独配置,位于客户端的常规选项卡中。客户端还必须允许 groups 作用域。

角色

角色支持应用中的基于角色的访问控制(RBAC)。在 Authagonal 中定义角色,将其分配给用户,并使用令牌中的 roles 声明在您的应用逻辑中执行授权。

管理角色

角色页面显示所有已定义角色的表格,支持内联编辑。每个角色包含:

列描述
名称角色的唯一标识符(例如 "admin"、"editor"、"viewer")
描述角色所授予权限的可读描述
创建时间角色的创建日期

创建角色

点击新建角色并提供名称和描述。角色名称应简洁,并在应用中遵循一致的命名约定(例如,使用小写加连字符:billing-admin)。

内联编辑

角色支持直接在表格中内联编辑。点击任意角色上的铅笔图标进入编辑模式——名称和描述字段变为可编辑。修改值后,点击对勾图标保存。更改立即生效。

删除角色

点击任意角色上的删除图标将其移除。系统会要求您在永久删除前进行确认。移除角色不会追溯使现有令牌失效——该角色将不会出现在删除后签发的新令牌中。

Roles table with inline editing active, showing editable name and description fields with save and cancel icons

在角色表格中内联编辑角色

令牌中的角色

当客户端请求 roles 作用域时,分配给用户的角色会作为 roles 声明包含在 ID 和访问令牌中。您的应用可以读取此声明来做出授权决策:

roles claim in ID token
{
  "sub": "user-123",
  "email": "[email protected]",
  "roles": ["admin", "billing-admin"]
}

SCIM 用户配置

SCIM 2.0(跨域身份管理系统)支持从 Okta、Azure AD、OneLogin 和 JumpCloud 等企业身份提供商自动配置用户和组。配置完成后,用户账户和组成员关系会自动从上游身份提供商同步到您的 Authagonal 租户。

SCIM provisioning flow: shows user lifecycle events flowing from enterprise IdP through Authagonal SCIM API to your app via TCC webhooks

SCIM 用户生命周期同步与下游配置

设置步骤

按照以下步骤为客户端启用 SCIM 用户配置:

  1. 选择客户端应用——选择 SCIM 配置将关联的 OAuth 客户端。
  2. 生成 SCIM 令牌——提供描述和到期天数,然后生成令牌。
  3. 立即复制令牌——原始令牌值仅显示一次。在关闭对话框前复制它。
  4. 配置您的身份提供商——在您的身份提供商的 SCIM 设置中,输入基础 URL 和 Bearer 令牌。
  5. 测试用户同步——从您的身份提供商触发测试同步,并验证用户是否出现在 Authagonal 门户中。

SCIM 基础 URL

使用以下基础 URL 配置您的身份提供商:

SCIM Base URL
https://{slug}.authagonal.io/scim/v2

将 {slug} 替换为您的租户 slug。

SCIM configuration page showing client selector, token generation form, and base URL

SCIM 设置页面,包含令牌生成功能

令牌管理

SCIM 令牌用于验证来自身份提供商的配置请求。您可以为每个客户端管理多个令牌:

字段描述
描述用于标识令牌的标签(例如「Okta Production SCIM」)
过期时间令牌有效期(天数,1 到 3650,默认 365)。
状态活跃令牌正在使用中。已撤销的令牌显示 Revoked 标识且无法再用于验证请求。

要撤销令牌,请点击令牌旁边的撤销按钮。已撤销的令牌在列表中仍然可见(用于审计目的),但会立即停止接受请求。

SCIM token list showing active and revoked tokens with description, expiry date, and revoke button

令牌管理,包含活跃和已撤销令牌指示器

立即复制令牌

原始 SCIM 令牌在创建时仅显示一次。请立即复制——之后无法再次获取。如果您丢失了令牌,需要生成新令牌并更新您的身份提供商配置。

测试连接

通过查询 ServiceProviderConfig 端点验证您的 SCIM 集成是否正常工作:

Test SCIM connectivity
curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://acme.authagonal.io/scim/v2/ServiceProviderConfig

成功的响应会返回一个 JSON 文档,描述支持的 SCIM 功能:支持 PATCH 和筛选,不支持批量操作、更改密码、排序和 ETag。

首选语言

SCIM 的 preferredLanguage 属性(回退到 locale)会映射到用户已存储的语言。通过 SCIM 配置的 SSO 用户会自动收到其 IdP 所发送语言的本地化邮件。

一个凭据能看到什么

一个 SCIM 凭据只能看到它自己配置的用户和组。读取、更新或删除由其他连接器创建的用户会返回 404,列表只返回它自己的,而一个组的成员也只能是同一个连接器配置过的用户。以任何其他方式创建的账户,无论是由管理员创建、通过自助注册创建,还是通过 SSO 即时配置创建,对 SCIM 都完全不可见。

这条边界是按客户端划分的,而不是按令牌。在同一个客户端上签发的两个凭据是拥有两个密钥的同一个身份,每一个都可以修改另一个创建的内容。请给互不信任的连接器各配一个客户端。externalId 的作用范围也是如此,因此两个连接器可以各自把 ext-001 用在不同的人身上而不会冲突。

取消配置

DELETE /scim/v2/Users/{id} 会停用该账户并将其标记为已删除。记录会按 RFC 7644 允许的方式保留,但对之后的每一次操作都返回 404,并且不会出现在列表中。该用户的 MFA 注册和组成员关系会被清除,其 externalId 映射会被释放,已签发的令牌全部被撤销,因此访问权限立即终止,而不是等到令牌下次过期。

如果同一个人后来被重新雇用并重新创建,他们会获得一个新的用户 id。标识符从不重复使用:该 id 是您签发过的每一个令牌中的 subject,因此重用它会在每个信任该 id 的应用中,悄无声息地把前一位持有者的历史交给新入职的人。

为已同步的用户标记组织

SCIM 没有提供任何方式让连接器说明它正在同步您的哪一个客户。核心 SCIM 未定义组织属性,因此一次普通的用户创建只会告诉您此人的姓名和邮箱,不会说明他们属于谁。如果多个客户都向您的租户配置用户,他们的用户到达时无法区分。

回答这个问题的是凭据,而不是请求本身。创建 SCIM 令牌时,在组织下选择您租户中的一个组织(租户中有了组织之后,就会出现该选择器)。通过该令牌配置的每个用户都会成为该组织的活跃成员,此后会在其令牌上以 org_id 声明的形式携带它。为每个客户在同一个客户端上签发一个凭据,他们同步过来的用户就能被正确归属,而不必为每个客户单独注册一个客户端。留空则用户不带标记。

打标记不等于隔离

所有权是按客户端强制执行的,而不是按令牌。同一个客户端上的两个凭据是拥有两个密钥的同一个身份:每一个都可以读取、重命名、停用和删除另一个创建的用户。当所有凭据都由您自己保管时,这没有问题。如果每个客户自己的 IT 团队各自保管他们的凭据,就为他们各配一个客户端,并同样在他们的凭据上设置组织。

标记只在用户被创建时应用,绝不会在之后的更新中应用,因此一次例行的增量同步不会悄悄改动已有账户的归属。如果您同时还运行一个配置应用,凭据上显式设置的标记优先:/try 响应只会填充仍然为空的组织。

支持的架构

我们实现了 SCIM 2.0 的核心 User 和 Group 架构(RFC 7643)。支持的用户属性有 userName、name.givenName、name.familyName、displayName、emails、active、externalId 和 preferredLanguage / locale。

enterprise user 扩展未实现,因此 department、manager、employeeNumber、costCenter、division 和 organization 会被接受并忽略,而不是被存储,create、replace 和 PATCH 都是如此。Entra 和 Okta 默认都会映射其中一部分,因此您无需把它们从属性映射中删除。要把某个用户归属到您的某个客户,请改为在 SCIM 凭据上设置组织:enterprise 的 organization 属性是由您客户的身份提供商断言的,它被有意设计为不会成为该用户的 org_id。

OAuth 作用域

作用域允许客户端请求用户数据或权限的特定部分。Authagonal 支持标准 OIDC 作用域和您为自己的 API 定义的自定义作用域。

内置作用域

作用域描述
openid任何 OpenID Connect 流程所必需。颁发 ID 令牌。
profile返回标准个人资料声明(name、given_name、family_name、locale、org_name)。
email返回用户的电子邮件地址和验证状态。
phone返回用户的电话号码(phone_number)。
offline_access与访问令牌一同颁发刷新令牌。
roles发出用户的 roles 声明。没有此作用域时,不会发出角色。
groups发出用户的 groups 声明(SCIM 组成员关系)。没有此作用域时,不会发出组。

自定义作用域

在作用域页面定义您自己的作用域。每个作用域描述客户端可以请求的权限或资源(例如 billing.read、orders.write)。

Custom scope creation form with name, display name, description and User Claims fields
字段描述
name在令牌请求中发送的作用域标识符(例如 billing.read)。
displayName在同意屏幕上显示的可读标签。
description同意时显示名称下方的较长说明。
userClaims授予此作用域时添加到访问令牌和 ID 令牌的额外声明。
showInDiscoveryDocument如果启用,作用域将出现在 /.well-known/openid-configuration 中。
emphasize在同意屏幕上将作用域作为敏感项突出显示。
required防止用户在同意时取消选择该作用域。
group同意分组:共用同一标题的作用域会在同意屏幕上归在一个复选框下一起显示。仅影响呈现方式。
allowedRoles用户必须持有其中某个角色才能被授予此作用域。留空则允许所有人;不具备其中任一角色的用户,其令牌中会去掉该作用域,而不会被拒绝。

同意集成

设置 RequireConsent: true 的客户端在首次请求时会提示用户。删除作用域不会撤销已颁发的令牌——如有需要请显式撤销。

令牌上的自定义声明

自定义声明包含两半。来源是按用户的数据:每个 AuthUser 都有一个 customAttributes 字典,可以通过 Portal(用户 → 用户 → 自定义属性)、SCIM 或 TCC 配置钩子来填充。释放按作用域进行:每个作用域的 userClaims 列表声明允许离开服务器的键。

当客户端请求作用域时,Authagonal 会遍历已授予的作用域,合并其 userClaims 列表,仅从用户的 customAttributes 中输出这些键。未知键会被静默丢弃 — 客户端无法通过猜测名称读取属性。标准 OIDC 声明 (sub、email、name 等) 遵循规范,不受白名单限制。

Example: project-scope claims on an access token
# 1. On the user (Portal → Users → {user} → Custom Attributes)
department = "engineering"
employee_id = "E-1042"
seat_tier = "enterprise"

# 2. On a custom scope (Portal → Scopes → projects.read)
name        = "projects.read"
userClaims  = ["department", "seat_tier"]   # <-- whitelist

# 3. Client requests scope=openid projects.read
# Decoded access token (relevant fields only):
{
  "sub": "u-9b…",
  "scope": "openid projects.read",
  "department": "engineering",
  "seat_tier":  "enterprise"
  // employee_id is NOT emitted; it's not in the whitelist for any granted scope.
}

联合声明按会话补缺

当用户通过上游 IdP(SAML/OIDC SSO)登录时,从 IdP 进入的会话级声明(例如从 SAML 断言映射出的 department 属性)流经同一作用域白名单,但只用于补缺:发生键冲突时,以已持久化的 customAttributes 值为准。它们出现在该会话的令牌上(并在刷新轮换中保留),但不会回写到用户记录。

将作用域分配给客户端

在客户端 → 作用域与授权选项卡中添加允许的作用域。客户端只能请求已被授予的作用域;未知的作用域将被拒绝,返回 invalid_scope。

品牌

自定义您的租户登录页面的外观和感觉。品牌设置让您将身份验证体验与产品的视觉形象相匹配——从 Logo 和颜色到高级 CSS 覆盖。

外观

设置项描述
appName显示在登录页面标题(未设置 logo 时)和事务性电子邮件中的应用名称
logoUrl您的 Logo 图片 URL。显示在登录页面顶部。推荐尺寸:200x60px 或类似宽高比。
primaryColor用于按钮、链接和焦点状态的主品牌颜色。通过颜色选择器或十六进制输入设置。更改值时实时预览会更新。
customCssUrl在默认样式之后加载的 CSS 文件 URL。它必须与登录页面同源提供;任何其他来源的 URL 都会被忽略。
Branding appearance settings with app name input, logo URL field, color picker with hex input, and custom CSS URL field

带实时颜色预览的外观设置

联系信息

设置项描述
supportEmail显示在登录页面上的支持电子邮箱地址。用户在需要帐户帮助时会看到此地址。

登录页面切换

控制租户登录页面上显示的元素:

开关描述默认值
showForgotPassword在登录表单上显示「忘记密码?」链接开启
showRegistration显示「注册」链接以支持自助用户注册开启
poweredBy在登录页面底部显示「Powered by Authagonal」标识开启
A customized login page showing a branded logo, custom primary color on the sign-in button, and support email in the footer

应用了自定义品牌的示例登录页面

自定义 CSS

如需完全控制登录页外观,请在品牌设置中提供 CSS 文件 URL。该文件会在默认样式之后加载,因此您的规则优先。该 URL 必须与登录页面同源;来自其他来源的样式表不会被加载。

CSS 自定义属性

登录页面支持 CSS 自定义属性(变量)以便进行常见覆盖。在您的 CSS 文件中设置这些变量,即可修改颜色、字体和形状,无需编写复杂的选择器。
/* your-custom-styles.css */
:root {
--auth-bg: #1a1a2e;
--auth-card-bg: #16213e;
--auth-heading: #e0e0e0;
--auth-radius: 12px;
--auth-font: 'Inter', sans-serif;
}
变量描述默认
--auth-bg页面背景色#f3f4f6
--auth-card-bg登录卡片背景white
--auth-heading标题文字颜色#111827
--auth-radius卡片圆角半径0.5rem
--auth-font字体族inherit

深色模式

登录应用提供浅色、深色和跟随系统三种主题。品牌设置页面上的深色模式设置用于选择默认值:关闭、自动(跟随系统偏好,即默认值)或强制。用户仍可在登录页面上通过切换按钮选择;选择会在会话间保留。当设置为 system 时,SPA 会实时跟踪 prefers-color-scheme。

浅色值在 :root 中声明;深色覆盖仅限于 .dark。通过 customCssUrl 设置的租户品牌样式始终优先——无论用户选择何种主题,您的颜色都会保留。

元素选择器

如需更细粒度的控制,请使用 data-auth 属性定位特定元素。这些选择器在更新之间保持稳定 —— 在我们更改内部类名时不会失效。
选择器元素
[data-auth="page"]整页背景容器
[data-auth="header"]Logo 和应用名称区域
[data-auth="logo"]Logo 图像
[data-auth="app-name"]应用名称标题(未设置 Logo 时)
[data-auth="content"]主要内容区域(表单、消息)
[data-auth="login-form"]登录表单元素
[data-auth="email-field"]电子邮件输入包装器
[data-auth="password-field"]密码输入包装器
[data-auth="submit-button"]登录按钮
[data-auth="languages"]语言选择器栏

设置

配置租户范围的安全策略、Webhook 和环境设置。这些设置全局适用于所有客户端,除非在客户端级别被覆盖。

密码策略

在“用户 → 设置”下定义租户中所有用户的密码复杂度要求:

设置项范围默认值
minPasswordLength6 – 1288
requireUppercase开 / 关开启
requireLowercase开 / 关开启
requireDigit开 / 关开启
requireSpecialChar开 / 关开启
Password policy settings showing minimum length slider and toggle switches for character requirements

密码策略配置

MFA 策略

租户范围的 MFA 策略在“用户 → 设置”下设置,决定默认的多因素身份验证行为。单个客户端可以覆盖此设置。

策略行为
DisabledMFA 不可用。用户无法注册 MFA。
EnabledMFA 为可选。用户可以选择注册,已注册的用户在登录时会收到提示。
RequiredMFA 为必需。所有用户必须注册 MFA 并在每次登录时完成第二因素验证。

会话与锁定

控制会话持续时间和账户锁定行为:

设置项范围默认值
sessionLifetimeMinutes5 – 43,200(30 天)60
maxFailedAttempts1 – 1005
lockoutDurationMinutes1 – 1,440(24 小时)10
Session and lockout settings with numeric inputs for session lifetime, max failed attempts, and lockout duration

会话和锁定配置

Webhook

Webhook 让你实时响应认证事件。两个事件 (onUserAuthenticated、onTokenIssued) 是可强制的 — 默认情况下它们异步触发,不会阻塞用户,但你可以按事件启用强制,使非 2xx 响应或 {"allow": false} 响应体拒绝该操作。其余事件都是通知 — 永远是触发即忘,从不阻塞。

事件类型描述
onUserAuthenticated可强制登录成功后触发。默认触发即忘,不影响登录延迟。开启 <code>webhookEnforceUserAuthenticated</code> 即可使其变为阻塞 — 非 2xx 响应或 <code>{"allow": false}</code> 响应体将拒绝登录。
onTokenIssued可强制在签发令牌之前触发 (authorization_code、refresh_token、client_credentials)。默认触发即忘。开启 <code>webhookEnforceTokenIssued</code> 即可使其变为阻塞 — 非 2xx 响应或 <code>{"allow": false}</code> 响应体将阻止令牌签发。
onUserCreated通知当新用户注册或通过 SCIM 配置时的即发即忘通知。
onUserUpdated通知当用户记录被更新(资料变更、角色变更、SCIM 更新)时的触发即忘通知。
onUserDeleted通知当用户被删除时的触发即忘通知(来自 Portal/SCIM,或由保留策略触发)。
onLoginFailed通知当登录尝试因凭证错误、锁定或策略拒绝而失败时的即发即忘通知。

其他 Webhook 设置:

设置项范围默认值描述
webhookTimeoutSeconds1 – 305等待强制执行 Webhook 响应的最大时间(超时前)
webhookFailOpen开 / 关开启启用后,如果强制执行 Webhook 不可达或超时,操作将被允许继续
Webhook configuration section showing URL inputs for each event type, timeout slider, and fail-open toggle

Webhook 事件配置

强制执行 Webhook 可用性

强制执行 Webhook 可以阻止身份验证流程。如果您的 Webhook 端点不可用且 webhookFailOpen 被禁用,则没有用户能够登录。除非您有严格的合规要求需要在 Webhook 失败时阻止,否则请使用失败开放模式。

验证 Webhook

一旦配置了任何 Webhook URL,Authagonal 就会为每个租户生成一个签名密钥(一个 whsec_… 值,在设置 → Webhook 下以只读方式显示)。每次出站投递都会带有一个 X-Authagonal-Signature: t=<unix>,v1=<hex> 头,其中 v1 为 HMAC-SHA256(secret, "{t}.{body}"),基于原始请求体计算。在你的端点上重新计算它并进行常量时间比较,以确认请求确实来自 Authagonal 且未被篡改 —— 并拒绝 t 过于陈旧的投递以阻止重放攻击。

验证 Webhook (Node.js)
import crypto from 'node:crypto';

// rawBody MUST be the exact bytes Authagonal sent: verify before any JSON re-serialization.
function verifyAuthagonalWebhook(signatureHeader, rawBody, signingSecret) {
  const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('=')));
  const { t, v1 } = parts;

  // Replay protection: reject deliveries older than 5 minutes.
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;

  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(`${t}.${rawBody}`)
    .digest('hex');

  return v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

轮换签名密钥

使用签名密钥旁的重新生成来轮换它 —— 例如在怀疑泄露之后。先前的密钥会立即失效,因此请用新值更新你的验证器,否则进行中的投递将开始无法通过签名校验。

维护窗口

为在存储分片之间迁移租户数据等破坏性操作设置首选维护窗口。选择一个 UTC 小时(0–23);门户还会显示对应的本地时区时间以方便参考。

注册与访问

谁可以成为您租户中的用户,以及他们在什么条件下可以登录。

设置项默认值描述
公开注册开启是否允许任何人自助注册。关闭后会隐藏注册页面并拒绝注册 API,这一点很重要:仅仅隐藏链接会让端点依然敞开。当您通过 SCIM、API 或邀请自行开通用户时,请使用它。
登录前要求验证电子邮箱开启未确认的地址无法登录。如果改由您自己的应用来对验证把关,可以关闭它;无论哪种方式,email_verified 声明都仍会随令牌一起发出,因此这个信号不会丢失。
动态客户端注册关闭允许客户端在运行时按 RFC 7591 自行注册,AI 智能体或 MCP 连接器在发起流程之前正需要这一点。默认关闭,启用后也只为您自己的租户打开这道门。无论如何,注册始终受速率限制,并始终要求 PKCE 和用户授权同意。
门户 MFA 策略禁用针对您自己团队登录门户的多因素设置,与面向终端用户的策略相互独立。可设为禁用、登录时可选,或强制要求。
用户数上限无默认情况下用户总数没有上限,也没有任何方案会设置上限。在租户所有者的电子邮箱地址未验证期间,会适用 5 个用户的临时上限;地址一经验证,该上限即永久解除。

不活跃用户保留

可选地停用并随后删除长期未使用的账户,避免目录里永远堆积着休眠身份。两者在您设置之前都处于关闭状态,且删除是永久性的。

设置项默认值描述
停用阈值(不活跃天数)从不停用在这段时间内未登录过的账户。记录会被保留,可以重新激活。
删除阈值(不活跃天数)从不永久删除该账户。没有撤销这一说,因此在开启它之前,请先设置下面的预警时间窗和 Webhook。
预警天数7在停用或删除发生前多少天触发预警 Webhook,给您留出干预的时间。
保留策略 Webhook-这些预警以及已执行的动作会被投递到哪里,方便您通知本人或让账户继续保留。

审计导出与远程备份

两种按计划、而非按需申请取出数据的方式。审计导出会把每一个已结束整天的审计事件推送到您指定的 URL,并附带签名以便您验证它来自我们,可用于接入 SIEM 或合规归档。远程备份会把您每日和每周备份的副本发送到您掌控的目的地,可选择不认证、基本认证或 Bearer 令牌。审计导出在审计日志页面的导出选项卡中配置,远程备份在备份页面中配置;两者的数据都归您保管,与我们保存的备份彼此独立。

沙箱环境

环境页面管理沙箱环境:这些是命名的、相互隔离的环境,拥有各自的用户、客户端、签名密钥、MFA 注册和授权,每个都可以通过单独的 URL 访问。品牌、方案和账单与正式环境共享。使用它们来测试配置更改、SSO 集成和 Webhook 端点,而不影响正式用户。默认最多可创建 5 个,活跃的沙箱用户会计入您的 MAU。

操作描述
添加环境创建一个新环境。名称为 1 到 20 个小写字母和数字,“live”为保留名称。新环境初始为空:不会从正式环境复制任何内容。
刷新清除该环境的数据并将其重置为空。
删除永久删除沙箱环境及其所有数据。

每个环境可通过 {name}-{slug}.authagonal.io 访问。

Environments page listing sandbox environments with their URLs and Refresh and Delete actions

沙箱环境控制

计费

通过门户的计费页面管理您的订阅和账单。此页面提供当前方案的概览,并提供访问 Stripe 计费门户以管理支付方式、发票和方案变更。

订阅信息

计费页面一目了然地显示您当前的订阅详情。您会看到一个状态标签,指示您的订阅状态——active、trialing、past_due、canceled 或 unpaid——以及您的方案名称、当前计费周期(起止日期),以及您的订阅是否设置在当前周期结束时取消。

管理订阅

点击管理订阅按钮,在新窗口中打开 Stripe 计费门户。在那里您可以更新支付方式、查看和下载发票、更改方案或取消订阅。

如果尚未创建订阅,则会显示设置计费引导操作,指导您选择方案并输入支付信息。

Billing page showing subscription status, plan name, billing period, and Manage Subscription button

计费页面显示您当前的订阅详情并提供 Stripe 访问入口

支付安全

所有计费通过 Stripe 处理。您的支付信息绝不会存储在 Authagonal 服务器上。

自定义域名

通过您自己的域名(例如 auth.yourdomain.com)而非默认的 {slug}.authagonal.io 提供身份验证页面。自定义域名为您的用户提供无缝的品牌化身份验证体验。

添加域名

在添加域名表单中输入您要使用的主机名(例如 auth.yourdomain.com)。添加后,该域名将以 pending_verification 状态显示在您的域名列表中。

DNS 验证

创建门户为该域名显示的两条 CNAME 记录,均指向 {slug}.authagonal.io:一条位于主机名本身,用于承载流量,可以启用代理;另一条位于 _authagonal-challenge. 加上该主机名,用于证明所有权,必须为仅 DNS(不代理)。记录就位后,点击检查 DNS进行验证。待验证的域名也会被自动重新检查。

CNAME records
auth.yourdomain.com.                        CNAME  acme.authagonal.io.
_authagonal-challenge.auth.yourdomain.com.  CNAME  acme.authagonal.io.

DNS 传播

DNS 传播最多可能需要 48 小时。如果验证失败,请等待后重试。

TLS 证书

域名验证通过后,您需要 TLS 证书以便用户可以通过 HTTPS 安全连接。Authagonal 支持两种选项:

自动(cert-manager)——Authagonal 使用 cert-manager 自动配置和续期 TLS 证书。这是大多数用户的推荐选项。无需额外配置。

自带证书(BYO)——以 PEM 格式上传您自己的证书和私钥。如果您的组织要求使用特定证书颁发机构的证书,此选项很有用。系统会跟踪证书到期时间,以便您在过期前续期。

域名状态

每个域名显示一个状态标签,指示其当前状态:pending_verification(DNS 尚未确认)、verified(DNS 已确认,TLS 待配置)或 active(完全可用)。

Domain list showing domains with status badges and verification controls

域名列表显示每个自定义域名及其当前状态

BYO certificate upload form with certificate and private key PEM fields

以 PEM 格式上传您自己的 TLS 证书和私钥

BYO 证书续期

请保持您的 BYO 证书已续期。过期的证书会导致用户的浏览器显示安全警告。

组织

组织是租户内的一个客户。租户仍然是隔离边界:一个表前缀和一个签名密钥,租户的每个主机都是各自的签发者。组织在租户内部划分身份,因此一个租户可以服务许多客户组织,而无需为每个客户创建一个租户。

组织与自定义域名(域名可以固定到某个组织)以及配置应用(配置应用可以为新用户标记一个组织)并列。有关此功能所服务的“一个租户,多个品牌客户”模式的端到端说明,请参阅多客户应用指南。

模型和 ID

每个组织都有一个稳定且不可变的 id(形如 org_<32 hex>),作为 org_id 令牌声明发出;还有一个不可变且在租户内唯一的 slug,作为 org_slug 发出,并被 organization 授权参数接受。ID 和 slug 共用同一个查找命名空间:与另一个组织的 ID 相同的 slug 会被当作冲突拒绝,与普通的重复 slug 完全一样。

字段可修改说明
id否org_id 声明。以 org_ 加 GUID 的形式生成,下划线不是合法的 slug 字符,因此新 ID 永远不会与 slug 冲突。
slug否org_slug 声明,也是 organization 参数所接受的值。不可变,因为依赖方会将其硬编码。
name是<code>org_name</code> 声明。可随意编辑。
metadata是自由格式、由租户控制的键值对。除非某个 scope 的声明集明确放出其中一项,否则绝不会出现在令牌中。
brandingJson是按字段合并到租户品牌之上的 JSON 对象,参见下文的品牌覆盖。
domains仅限域名端点组织已声明的电子邮件域名,每个域名附带用于证明所有权的 DNS 记录。只能通过下文的域名端点修改,不能通过更新操作修改。

成员关系

成员关系是用户与组织之间的多对多关联,每个(组织,用户)对一行。一个用户可以拥有多个成员关系;授权以组织身份登录的是成员关系记录,而不是旧版的每用户组织标签。

状态是否授予令牌?
invited已邀请但尚未接受。不授权签发令牌。
active状态良好的成员。唯一授权签发令牌的状态。
suspended在不删除记录的情况下撤回成员关系。不授权签发令牌。

成员关系的 roles 取自租户现有的角色目录,并合并到 roles 声明中,但只有当请求明确选定了该组织(参见令牌的组织选择)且角色来自处于 active 状态的成员关系记录时才会合并。在一个组织中持有的角色绝不会进入为另一个组织签发的令牌。

邀请加入组织

POST /api/v1/users/invite 接受可选的 organizationId 和 organizationRoles。一次调用即可创建带有 invited 成员关系的账户,并发送带有该组织品牌的邀请邮件。如果组织有固定的自定义域名,链接会在该域名上打开。对方接受后,成员关系变为 active。指定组织需要所有者或管理员权限。

通过 SCIM 向组织配置用户

SCIM 令牌可以绑定到某个组织(在 POST /api/v1/scim/tokens 上使用 organizationId,或在创建令牌时使用“组织”选择器)。该 ID 必须是租户中某个组织的 ID。该令牌创建的每个用户都会成为 active 成员,并以 org_id 携带该组织。绑定仅在创建时生效,并且它决定的是成员关系而非访问权限:同一客户端上的两个令牌仍然能看到彼此的用户。通过任何途径删除用户都会移除其成员关系。

策略标志

enabled(默认开启):被禁用的组织不会为自己签发令牌,授权时以及每次刷新时都会检查,因此禁用组织会在下一次轮换时终止现有会话,而不只是阻止新的登录。

requireMembershipForTokens(默认开启):用户是否必须拥有 active 成员关系才能获得该组织的令牌。它只对明确的选择生效(请求中指定了该组织、刷新时携带了它、组织范围的连接,或仅限于这一个组织的客户端)。仅仅从账户自身的旧版标签继承而来的组织绝不会受此标志限制。

allowAutoMembership(默认关闭)允许用户在其已确认的电子邮件地址属于该组织某个已验证域名时无需邀请即可加入。它在授权时以及每次刷新时强制执行。请参阅下文的电子邮件域名与自动成员关系。

电子邮件域名与自动成员关系

组织声明一个电子邮件域名,并通过 DNS TXT 记录证明自己控制该域名。开启 allowAutoMembership 后,已确认电子邮件地址属于该域名的用户在首次以该组织身份登录时会成为成员。

  1. 声明域名。使用该域名调用 POST /api/v1/organizations/{id}/domains。响应为 201,包含 recordName(_authagonal-org.acme.com)和 recordValue(authagonal-org-verify=<token>)。令牌是随机的,每次声明各不相同。
  2. 发布记录。在该域名的 DNS 中添加一条名称和值与之完全一致的 TXT 记录。
  3. 验证。POST /api/v1/organizations/{id}/domains/{domain}/verify 会查询该记录。匹配时返回 200 和 verified: true。未匹配时返回 409 verification_failed,绝不会返回 200 加 verified: false,因此轮询成功状态的客户端不会把未匹配误认为成功。
  4. 移除。DELETE /api/v1/organizations/{id}/domains/{domain} 返回 204。现有成员关系保留,只是以后不再自动加入。
POST /api/v1/organizations/{id}/domains
POST /api/v1/organizations/org_7fa2c9e1.../domains
Content-Type: application/json

{ "domain": "acme.com" }

201 Created
{
  "domain": "acme.com",
  "verified": false,
  "verifiedAt": null,
  "createdAt": "2026-10-03T01:02:03Z",
  "recordName": "_authagonal-org.acme.com",
  "recordValue": "authagonal-org-verify=Qm9...base64url"
}

拒绝情形:400 domain_invalid(不是纯 DNS 名称)、409 domain_exists(已在此组织上)、409 domain_taken(租户中的另一个组织持有它)、404 organization_not_found 和 404 domain_not_found。一个域名在每个租户中至多属于一个组织,验证时会再次检查。

自动成员关系规则

当以下所有条件都成立时,选择器会接纳没有 active 成员关系的用户:

  • 该组织被明确选定:通过携带的刷新授权、组织范围的连接、organization 参数(域名固定会提供它),或仅限于一个组织的客户端。账户自身的旧版标签绝不会触发自动加入。
  • 该组织已启用且 allowAutoMembership 为开启状态。
  • 用户的电子邮件已确认,因为任何人都可以注册 [email protected]。
  • 电子邮件中最后一个 @ 之后的部分转为小写后,与该组织某个已验证域名完全相等。不做子域名匹配:已验证的 acme.com 不会接纳 [email protected]。请分别声明并验证每个子域名。

它在成员关系检查运行之前写入的内容:没有记录则新建一条没有角色的 active 成员关系;invited 记录会提升为 active,角色保持不变;active 记录保持不动;suspended 记录永远不会被提升。

删除成员并不能把他们挡在外面

只要 allowAutoMembership 开启,仍然符合条件的已删除成员会在下次授权或刷新时重新加入。要把符合条件的用户挡在外面,请将其成员关系设为 suspended,这是规则唯一不会改变的状态。

令牌的组织选择

请求解析到哪个组织只在一个地方决定,因此 /connect/authorize、每次刷新以及设备授权上的结果完全一致。优先级从高到低:

#来源行为
1携带的授权(刷新)先前令牌所签发给的组织。如果它已不存在,刷新本身会被拒绝,而不会回退到其他任何内容。
2组织范围的连接会话通过属于某个组织的 SAML 或 OIDC 连接登录,因此选定该组织。这是唯一经过证明而非仅凭声称的来源。指定了不同组织的请求会被拒绝,连接所属的组织已不存在时也会被拒绝。
3organization先按 slug 解析,再按 id 解析。域名固定也会到达这同一分支:中间件在请求被评估之前追加它。指定了但找不到会被直接拒绝,绝不会悄悄回退。
4仅限于一个组织的客户端自动选定:每个客户专用的应用只需注册其唯一的组织一次,并且从不发送参数。仅限于<em>多个</em>组织且没有提示时,会以 <code>account_selection_required</code> 拒绝。
5账户自身的旧版组织标签单个扁平字符串,通过一次查找按 id 解析,而不是扫描用户的成员关系。如果它不对应任何真实的组织记录,则原样发出且不做任何限制,与组织功能出现之前完全一样。它绝不是明确选择,因此绝不会自动加入。
6以上皆无令牌中完全没有组织:没有 <code>org_id</code>、<code>org_slug</code> 或 <code>org_name</code> 声明。

成员关系从不充当提示

拥有多个 active 成员关系但请求中没有指定组织的用户,其行为与没有任何成员关系的用户完全相同:不会查询成员关系表来选取默认值。选择会直接落到账户自身的旧版标签(第 5 行),如果那也为空,令牌就完全不带组织。成员关系只有在组织已被明确指定之后才开始起作用,此时会检查 requireMembershipForTokens,并在适用时尝试自动加入。

域名固定

自定义域名可以固定到一个组织,使该域名成为该组织自己的入口。租户解析中间件对 GET /connect/authorize(追加到查询字符串)和 POST /connect/par(改写推送的请求体,因为 PAR 从存储的负载中读取参数)都会强制执行这一固定。完整机制请参阅自定义域名。

PUT /api/v1/custom-domains/{domain}/organization
PUT /api/v1/custom-domains/auth.acme.com/organization
Content-Type: application/json

{ "organizationId": "org_7fa2c9e1..." }

固定的域名会拒绝冲突的请求

指定了与固定值不同的组织的请求会被直接以 400 access_denied 拒绝,在显示任何登录页面之前。否则依赖方可以发送自己的 organization 参数,固定就成了摆设。

组织范围的连接

SAML 或 OIDC 连接可以属于一个组织。通过它登录会选定该组织,并在用户没有成员关系时创建一条 active 成员关系。invited 记录会被接受:变为 active,并保留其角色和邀请人,因此通过自己的 IdP 登录的受邀者无需再用邮件。suspended 成员保持暂停,并在签发令牌时被拒绝。这不依赖于 allowAutoMembership 或域名,因为组织已经由连接选定。

登录页面上的组织名称

登录页面在应用名称下方显示登录到 {name}。名称来自与页面品牌相同的那个组织:先是固定域名的组织,然后是授权请求中指定的组织,最后是客户端唯一受限的组织。如果没有组织,或者组织名称与应用名称相同,则隐藏,因此标题永远不会出现“Acme / 登录到 Acme”。

品牌覆盖

组织的 brandingJson 会按字段合并到租户的登录页面品牌之上:存在且非空的键会覆盖,缺失或为 null 的键则继承,未知的键会被忽略。格式错误的 JSON 会退回到租户自己的品牌并记录一条警告,绝不会导致页面加载失败。

“品牌”选项卡可编辑全部 21 个可合并字段:外观(appName、logoUrl、primaryColor、customCssUrl)、浅色和深色模式颜色、电子邮件颜色、supportEmail、showForgotPassword、poweredBy 和 languages。每个字段要么继承租户的值(以提示形式显示),要么覆盖它,并带有一个“清除”控件用于移除该键。

showRegistration 不能被覆盖

是否提供注册已经由租户的公开注册设置决定,并且无论品牌如何,注册端点都会强制执行。组织不是该决定的权威,因此这一个字段被排除在合并之外,“品牌”选项卡上也没有对应的控件。

从旧版组织标签回填

在组织功能出现之前,用户可以携带一个没有任何记录支撑的自由文本组织标签。POST /api/v1/organizations/backfill 会将其迁移为真实的组织和成员关系,按每个不同的旧版值分组。除非请求体为 { "dryRun": false },否则它只是一次试运行。

  • 有上限:每次调用最多扫描 50,000 个用户。响应为 truncated 表示需要再次运行;已迁移的用户在重新运行时会被跳过。
  • 幂等:已迁移的某个不同旧版值通过内部标记匹配,而不是通过组织(可编辑的)显示名称匹配,因此之后重命名生成的组织不会在下次运行时造成重复。
示例响应(试运行)
{
  "dryRun": true,
  "organizationsCreated": 3,
  "membershipsCreated": 41,
  "usersSkipped": 0,
  "organizations": [
    { "legacyValue": "acme-corp", "slug": "acme-corp", "users": 22 }
  ],
  "truncated": false
}

门户界面演示

组织页面列出租户中的每个组织,每次 50 个并带有加载更多按钮,提供新组织(仅 slug 和名称,策略标志、域名和品牌之后再设置),以及从旧组织字段回填流程,该流程先以预览方式运行上述回填,然后再“应用”。

Organizations list showing slug, name, and created-date columns, with New Organisation and Backfill from legacy organisation field controls

组织列表显示租户中的每个客户组织及其 slug、名称和创建日期

每个组织的详情页有三个选项卡:常规(名称、只读的 slug、三个策略开关、用于添加、验证和移除电子邮件域名并复制 TXT 记录的邮箱域名卡片,以及需输入 slug 确认的删除),品牌(每个可覆盖的字段,各自继承租户的值或覆盖它),以及成员(按电子邮件添加,账户不存在时会提供邀请,每行可更改状态和移除,带有“加载更多”)。

Organization detail page's Members tab showing invited, active, and suspended member rows with per-row status control

“成员”选项卡列出每个成员关系及其状态,并在每行提供更改或移除的控件

Organization detail page's Branding tab with each overridable field showing either the tenant's inherited value or an override with a Clear control

“品牌”选项卡为该组织逐字段覆盖租户的登录页面品牌

域名页面带有域名到组织的选择器:每个域名卡片都有一个内联下拉菜单,列出租户的组织,外加一个用于清除固定的“租户(无)”选项,更改后立即生效。添加域名时也会出现同样的选择器。

Domain card showing the inline organisation selector that pins a custom domain to one organisation

域名卡片上的内联组织选择器将该自定义域名固定到一个组织

审计事件

organization.created · organization.updated · organization.deleted · organization.member.added · organization.member.invited · organization.member.joined · organization.member.updated · organization.member.removed · organization.domain_added · organization.domain_verified · organization.domain_removed · organization.backfill · domain.organization_set

分页

组织列表和成员列表采用游标分页,返回 { items, nextCursor }。传入 limit(默认 50,限制在 1 到 200 之间),从第二页起,将上一次响应中的 nextCursor 原样作为 cursor 传入。nextCursor 为 null 表示最后一页。游标是基于组织 id(成员则为用户 id)的不透明键集位置,因此在两次调用之间增删记录不会导致页面偏移或重复。列表未签发的游标会得到 400 invalid_cursor。

通过 AI 助手管理组织

托管的门户 MCP 提供十三个组织工具,全部要求租户管理员角色:列出、获取、创建、更新和删除组织;列出、添加、更新和移除成员;添加、验证和移除电子邮件域名;以及列出用户所属的组织。它们调用与上述 API 相同的操作,因此工具不会与其路由出现偏差。请参阅通过 AI 助手运行您的门户。

每个主机一个签发者

租户的每个主机都是各自的 OIDC 签发者。当请求主机通过租户的域名表解析时,签发者以及发现文档公布的每个端点 URL 都是 https://{host},因此每个活动的自定义域名都声明自己的 iss,而租户的规范主机保持不变。签名密钥按租户划分,所以每个主机提供同一组密钥。这使得一个租户可以对应多个客户,每个客户使用自己的品牌域名并固定到自己的组织。请参阅自定义域名。

电子邮件配置

配置您的租户如何发送事务性电子邮件,例如验证、密码重置和邀请邮件。可在默认共享发件人、通过 Resend 验证的自定义域名,或自己的 SMTP 服务器之间进行选择。

Email settings showing provider options (default, Resend domain, custom SMTP) and a test-send

本地化邮件

事务性电子邮件以收件人的首选语言发送。验证、密码重置、账户已存在、欢迎、邀请、账户删除、支持和账单邮件均有十一种语言的模板:英语、德语、法语、西班牙语、葡萄牙语、越南语、简体中文、日语、阿拉伯语、印地语和南非荷兰语。当收件人的语言没有对应模板时,邮件将回退到英语。

语言在发送时根据收件人已存储的偏好来确定。该偏好可来自多个位置:

  • 注册和创建账户 —— 取自用户在托管登录界面上所选的语言。
  • 门户中的用户页面 —— 由管理员在创建或编辑用户时设置。
  • SCIM 配置:当用户通过 SCIM 同步时,从 IdP 的 preferredLanguage(或 locale)映射而来。
  • 自助账户页面 —— 由用户本人在 /login/account 上选择。

无需配置

本地化是自动的,并适用于所有提供商模式(默认、Resend 自定义域名和 SMTP)。无需启用任何内容。

电子邮件提供商

提供商描述设置
Authagonal (Default)电子邮件通过我们共享的 Resend 基础设施从 [email protected] 发送。无需配置 —— 开箱即用。
Custom Domain (Resend)通过 Resend 从您自己已验证的域名发送电子邮件。注册您的域名,添加 DNS 记录,验证所有权。
Custom SMTP通过您自己的 SMTP 服务器发送电子邮件。提供 SMTP 主机、端口、凭证和 TLS 设置。

发件人身份

发件人电子邮件和名称在所有提供商模式中共享。自定义域名(Resend)和自定义 SMTP 提供商必须填写发件人电子邮件;发件人名称为空时将回退到品牌设置中的应用名称。

字段描述
emailSenderEmail发出电子邮件的 From 地址。在自定义域名(Resend)模式下必须位于已验证的域名下。
emailSenderName收件人收件箱中显示的名称。

Resend 自定义域名

仅需在 Resend 验证一次您的发送域名,然后即可用作此租户的 From 地址。DNS 记录(SPF、DKIM)显示在“设置 → 邮件”的“自定义发送域名”面板中;点击<strong>检查验证</strong>时,Resend 会检查这些记录。

自定义 SMTP

使用您自己的 SMTP 服务器 —— 适用于内部中继、Resend 未覆盖的供应商或法规要求固定的场景。

字段描述
smtpHostSMTP 服务器主机名(例如 smtp.example.com)。
smtpPort连接端口,默认 587。TLS 通过 STARTTLS 协商;不支持端口 465 上的隐式 TLS。未认证的内部中继请使用 25。
smtpUsername认证用户名(可选 —— 未认证中继请留空)。
smtpPassword认证密码。以加密方式存储在租户设置密钥中。
smtpUseTls要求 TLS。除非指向可信的内部中继,否则请保持启用。

自定义发送域名

使用自定义域名(Resend)提供商时,您可以注册自己的域名,让电子邮件来自您的品牌(例如 [email protected]),而不是 @authagonal.io。

  1. 进入“设置 → 邮件”,选择自定义域名(Resend)提供商。
  2. 输入您的域名并点击“注册域名”。
  3. 将显示的 DNS 记录(DKIM、SPF 和 return path)添加到您域名的 DNS 中。
  4. 点击"检查验证"—— DNS 生效后(通常 1–10 分钟),域名状态将变为已验证。

DNS 传播

DNS 变更在全球传播最多可能需要 48 小时,但大多数提供商会在几分钟内更新。您可以根据需要多次检查验证状态。

测试

使用"设置 → 电子邮件"中的"发送测试邮件"按钮验证配置。系统将使用当前保存的设置向您的管理员邮箱发送测试邮件。

审计日志

审计日志提供对租户执行的所有管理操作的只读记录。通过门户或 API 进行的每项更改都会被记录完整的上下文,为合规和故障排除提供完整的追踪记录。

日志列

列描述
时间戳操作发生的日期和时间
执行者执行操作的管理员的电子邮箱地址,自动操作显示为 "system"
操作执行的操作类型(例如 Client Created、Settings Updated)
实体操作目标,格式为 type:id(例如 client:my-app)
详情关于变更的附加上下文

跟踪的操作

以下管理操作会记录在审计日志中:

类别操作
客户端Client Created、Client Updated、Client Deleted
SSO 连接SAML Connection Created、SAML Connection Deleted、OIDC Connection Created、OIDC Connection Deleted
用户User Created、User Updated
设置Settings Updated、Branding Updated
域名Domain Added、Domain Verified、Domain Deleted
SCIMSCIM Token Created、SCIM Token Revoked
角色Role Created、Role Updated、Role Deleted
组Group Created、Group Deleted
团队Team Member Invited、Team Member Removed
Audit log table showing timestamped administrative actions with actor, action, entity, and detail columns

审计日志提供所有管理操作的完整记录

保留

审计日志保留 365 天,不可修改或删除。如需保留更久,请在审计日志页面的导出选项卡中开启自动导出。

备份

Authagonal 每小时自动备份您的租户数据。备份包括所有用户、组、角色、客户端、SSO 连接、SCIM 令牌、品牌信息和设置。您可以在"备份"页面查看备份历史并下载最新的完整备份。

Backups page showing backup history with timestamps, types, and entity counts

备份工作原理

  • 每天一次将每小时的增量备份汇总为新的完整备份,每逢周日再将每日完整备份汇总为每周备份。保留七份每日备份和四份每周备份。
  • 增量备份每小时运行一次,仅捕获自上次备份以来发生变化的行。
  • 备份存储在 Azure Blob Storage 中,使用与您的租户相同的托管标识。
  • 已删除的记录通过 tombstone 跟踪并包含在备份中,以确保审计的完整性。

下载备份

点击"下载最新"以获取 ZIP 文件,其中包含最新的完整备份以及随后合并的所有增量备份。每个表都导出为 JSONL 文件(每行一个 JSON 对象)。

备份格式

备份以 JSONL(JSON Lines)格式导出 —— 每个表每行一个实体。此格式易于解析、比较并导入到其他系统。

配置应用

预配应用是你自己的服务。每当创建用户时,Authagonal 都会调用它们,让它们可以开通账户、分配许可证、决定该用户属于哪个组织,或者直接拒绝这次注册。

工作原理

创建用户时,Authagonal 会按 TCC(Try/Confirm/Cancel)模式调用你的预配应用的回调 URL。在其中任何一个应用被提交之前,所有应用都必须在 Try 阶段接受,这样多个下游系统可以达成一致,或者由其中一个否决,而不会留下创建了一半的账户。

阶段端点用途
/tryPOST {callbackUrl}/try检查应用是否可以处理该用户。返回 200 表示接受,4xx 表示拒绝。
/confirmPOST {callbackUrl}/confirm在所有应用接受 /try 阶段后提交操作。
/cancelPOST {callbackUrl}/cancel如果在 /try 阶段另一个应用失败,则回滚操作。

预配何时运行

每一条创建用户的路径都会运行预配,不只是自助注册。已经完成预配的应用与用户组合会被跳过,因此一个应用对每个用户只会看到一次。

创建路径何时触发
POST /api/auth/register自助注册
SAML ACS 回调新用户的首次 SSO 登录(JIT)
OIDC 回调新用户的首次 SSO 登录(JIT)
POST /scim/v2/Users连接器从客户的目录中预配用户
门户和管理后台创建用户运维人员手动创建或邀请用户

Try 请求

Authagonal 会把这段 JSON POST 到 <code>{callbackUrl}/try</code>。没有值的字段会被省略,而不是以 null 发送。

字段类型描述
transactionIdstring标识本次预配事务。同一个值也会发送给 /confirm 和 /cancel,因此请围绕它暂存你的工作,并在那次调用到达时提交或丢弃。
userIdstring该用户的 Authagonal id。这就是你会在他们的令牌中看到的 subject。
emailstring该用户的邮箱地址。
firstNamestring名,在创建路径提供了它时。
lastNamestring姓,在创建路径提供了它时。
organizationIdstring该用户已经所属的组织,如果有的话。只有在此前已有某处为其分配过组织时才会出现;首次注册时它不存在,这正是提示你去分配组织的信号。
customAttributesobject该用户已保存的自定义属性。对于通过 SSO 创建的用户,其中包含 federated_connection,即为其做出担保的那个连接的名称。

通过 SSO 到来的用户是最值得提前考虑的常见情况:他们还没有组织,而 federated_connection 会告诉你他们来自你的哪一个客户。

通过 SSO 连接到来的用户的 Try 请求
{
  "transactionId": "8f14e45fceea167a5a36dedd4bea2543",
  "userId": "0f6b1c8e-3d2a-4f51-9e77-2c1a4b5d6e7f",
  "email": "[email protected]",
  "firstName": "Ada",
  "lastName": "Lovelace",
  "customAttributes": {
    "federated_connection": "acme-okta"
  }
}

Try 响应

你的应用返回 200 和一个 JSON 主体。这个主体不只是一次确认收到:下游应用正是通过它来分配最终出现在用户令牌上的组织和属性。

字段类型描述
approvedboolean此应用是否接受该用户。省略时默认为 true。false 会拒绝这次注册,新建的账户会被删除。
reasonstring该用户被拒绝的原因。会呈现给调用创建路径的一方。
organizationIdstring该用户所属的组织。会保存在用户上,并作为 org_id 声明发到其令牌中。仅在用户尚未拥有组织时才会应用,因此第一个作答的应用胜出,后续应用会看到这次分配。
customAttributesobject要按键逐个合并到用户上的属性。通过某个 scope 的 UserClaims 配置发到令牌中。
emailVerifiedboolean你的应用担保它已验证过这个地址,例如通过兑换发送到该地址的邀请。Authagonal 会把账户标记为已确认,并跳过自己的验证邮件。
分配组织的 Try 响应
{
  "approved": true,
  "organizationId": "org_acme",
  "customAttributes": { "org_role": "member" }
}

org_id 就是从这里来的

账户的组织标签,也就是请求未指明组织时的 org_id 声明,就是你的预配应用作为 organizationId 返回的那个值。当它是租户中某个组织的 id 时,令牌会携带该组织的 org_id、org_slug 和 org_name,并且已停用的组织会拒绝登录。任何其他值都会原样作为 org_id 发出,没有唯一性规则,也没有格式要求。返回组织 id 不会创建成员关系。你也可以用 PUT /api/v1/users/{userId} 直接设置它,并用 GET /api/v1/users?organizationId= 按它过滤。

为用户打上正确的组织标记

在这里、在同一个地方决定组织,而不是在每条创建路径上各决定一次。SSO 用户会带上 federated_connection,它标识出为其完成认证的连接,因而也标识出客户,并且在同一个客户联合了多个邮箱域名时依然正确。受邀用户没有连接,因此请用你签发的邀请来匹配他们。两条路径都会在用户存在之前到达 /try,因此一段逻辑就能覆盖两者,也不会有两套规则各自漂移。

在 Try 回调中解析组织
// POST {callbackUrl}/try
app.post('/provisioning/try', async (req, res) => {
  const { email, customAttributes } = req.body;

  // An SSO user carries the connection that vouched for them. That is the
  // customer, and it stays right when one customer has several domains.
  const connection = customAttributes?.federated_connection;

  const org = connection
    ? await orgByConnection(connection)
    : await orgByPendingInvite(email);

  if (!org) return res.json({ approved: false, reason: 'No organization for this user' });

  // Stamped on the user and emitted as org_id on every token from now on.
  res.json({ approved: true, organizationId: org.id });
});

拒绝会删除账户

只要有任何一个应用返回 approved: false,刚创建的用户就会被删除,不会留下预配到一半的账户。API 创建路径会返回 422 以及你给出的 reason;SAML 和 OIDC 回调返回 400。对于你无需做任何处理的用户,请返回 approved: true。

添加配置应用

要添加配置应用,请提供应用名称、回调 URL、可选的API 密钥,以及可选的Try 超时(秒,默认 60,范围 5 到 300;Confirm 和 Cancel 使用固定的较短超时)。API 密钥作为 Bearer 令牌在每个 Webhook 请求的 Authorization 头中发送,允许您的应用验证来自 Authagonal 的请求。

测试

点击任意配置应用旁边的测试,向您的回调 URL 发送测试请求。测试结果显示 HTTP 状态码和响应正文,帮助您验证应用是否正确接收和处理 Webhook。

Provisioning apps page showing configured apps with test results displaying HTTP status and response body

测试配置应用以验证 Webhook 交付和响应处理

方案限制

每个租户的最大配置应用数量可配置,默认限制为 6 个。如果您的工作流程需要更多配置目标,管理员可以调整此限制。

API 密钥身份验证

如果设置了 API 密钥,它将作为 Bearer 令牌在 Authorization 头中发送。使用此密钥验证来自 Authagonal 的 Webhook 请求。

团队

团队页面管理门户管理员:可以通过管理门户访问和配置您的租户的人员。每位团队成员都有一个角色(Owner、Admin、Developer 或 Support),决定其可以更改哪些内容。

管理员列表

管理员列表显示每位团队成员的姓名、电子邮箱、角色和添加日期。当前用户行旁边会显示「您」标识,方便您识别自己的账户。

邀请管理员

要邀请新团队成员,请提供其电子邮箱、姓名和角色。对方会收到一封电子邮件,其中包含用于设置密码并激活账户的链接;该链接 7 天内有效。

邀请字段

管理员邀请会创建一个待激活的账户,并通过电子邮件向受邀者发送激活链接。

字段描述
email新管理员的邮箱地址。在租户中必须唯一。
name在管理员列表中显示的名称。
role要授予的角色:tenant:admin、tenant:developer 或 tenant:support。默认为 tenant:admin。所有者角色无法通过邀请授予。

移除管理员

租户所有者可以点击任意团队成员旁边的移除以撤销其访问权限。移除确认前会显示确认对话框。您不能移除自己,并且租户始终保留其所有者。

Team page showing the admin list with name, email, date added columns, a You indicator, and invite/remove controls

在团队页面管理门户管理员

所有权

每个租户有一位所有者。只有所有者可以移除团队成员或转移所有权,方法是对另一位成员使用设为 Owner;原所有者随后成为 Admin。

支持

无需离开门户,即可向 Authagonal 团队提交支持请求。每个请求都是一段连续的对话,从首次反馈到最终解决,您和我们的团队始终保持同步。

您的请求

支持页面会列出您提交的每一个请求,最新动态排在最前。借助状态标识,您可以一眼看出哪些在等您、哪些在等我们。

Support ticket list showing each ticket's subject, status badge, priority, and last-activity time, with a New ticket button

您的支持请求,含主题、状态、优先级和最近动态

  • 每一行显示主题、当前状态(进行中、等待回复、已解决或已关闭)、优先级以及最近动态的时间。
  • 点击新建请求即可创建一个,然后填写主题、优先级和您的第一条留言。
  • 彩色状态标识让您能轻松扫一眼,找出需要您处理的请求。

请求对话

打开请求即可看到完整对话。回复按顺序显示,我们团队的新消息无需刷新页面即可出现。

Support ticket thread showing the conversation between the customer and the Authagonal team with a reply box and file attachment control

您与 Authagonal 团队之间的请求对话

  • 您与 Authagonal 团队之间的对话消息按时间先后顺序显示。
  • 可在对话中直接回复并附加文件,分享日志、截图或配置。
  • 对话实时更新,我们团队一发出回复就会立即显示。
  • 如果您改为直接回复通知邮件,您的消息也会自动归入对话。

回复如何送达您

Authagonal 工作人员在管理端处理请求。每当团队回复时,您都会收到邮件通知,因此无需一直开着门户就能跟进对话。

面向您用户的支持台

除了您从我们这里获得的支持之外,Authagonal 还可以为您的终端用户运行一个支持台。他们在您租户自有品牌主机上的账户页面里提交工单,您的团队则在门户中回复。

它之所以存在,是因为登录不了的人,恰恰就是够不到那些必须登录才能用的支持工具的人。支持台紧挨着登录页面,所以被挡在门外的用户仍有一条路可走,而且每张工单送达时就已经关联到您目录中的真实账户,而不是关联到某人随手填写的地址。

启用它

在门户中打开客户支持,切换到其设置选项卡,然后开启启用客户支持。该选项卡对所有者和管理员可见。在您开启之前,您的用户看不到任何内容。

设置项作用
启用客户支持总开关。关闭时,终端用户页面和您的客服收件箱都会隐藏,对应的 API 返回 404。
允许来自未登录访客的请求让未登录的访客也能提交工单,也就是被挡在门外的那种情况。该入口由人机验证保护,而且由于没有账户可供登录,后续往来通过电子邮件加一个私密链接继续进行。
电子邮件通知工单到达或用户回复时通过电子邮件通知谁:无人、指定地址,或您的整个支持团队(所有者、管理员和支持人员),这样就不必有人一直盯着收件箱。
Default customer language当我们尚不知道用户的语言时,为其工单和电子邮件假定的语言。用户自己保存的语言始终优先;如果什么都没设置,则使用英语。此项位于设置的<strong>常规</strong>选项卡中。
支持 Webhook URL用于 POST 工单事件的 URL,可把事件推送进您自己的工具链。

免费方案不提供

支持台是免费方案唯一不包含的能力,因为入站邮件和翻译带有实实在在的按次成本。所有付费方案都包含它。身份验证功能从不会以这种方式设限:单点登录、SCIM、多因素、自定义域名、品牌定制和审计在每个方案上都有,包括免费方案。

您的用户看到什么

已登录用户会在您租户主机上的账户页面里看到一块带有您品牌的支持区域。他们可以提交工单、查看自己提交过的全部工单,并在对话中回复。您团队的回复也会通过电子邮件送达,因此用户不必反复回来查看。

启用匿名工单后,登录不了的人仍然可以提交工单。他们留下一个电子邮箱地址和自己的问题描述,随后拿到一个通往该对话的私密链接。这个链接是唯一的入口,因此请把它当作凭据看待:它无法被猜出,而任何持有它的人都可以阅读并回复那一个对话。

客服收件箱

您的团队在门户侧边菜单的客户支持中回复。它与您向我们提交的工单是彼此独立的两个地方,并且对 tenant:support 及以上角色开放,因此您可以只把支持台的权限给某位客服人员,而不必把门户的其余部分也交出去。

操作作用
回复发布到对话中。用户会收到电子邮件,如果他们正开着页面,也会实时看到。
分配把工单指派给您团队中的某位具体成员,避免两个人回复同一张工单。
状态与优先级让工单在进行中、等待回复、已解决和已关闭之间流转,并标记它有多紧急。已关闭的工单会在保留期结束后删除,而不是永久留存。
内部备注只有您的团队可见、用户永远看不到的备注。编辑和删除都会记入审计日志。
代用户创建工单从您的目录中选出某位用户并与其开启一段对话,适用于交流最初发生在别处的情况。
升级给 Authagonal如果一张工单最终是关于 Authagonal 而不是您的产品,可以把它升级上来。这会在我们团队这边打开一张关联工单,并可选择带上目前为止的对话内容,同时把两者关联起来,方便您同时跟进。一张工单只能升级一次。

用户用自己的语言书写

工单按用户书写时使用的语言保存,而对话中的每个人都用自己的语言阅读它。您的客服人员看到的是被翻译成其门户语言的消息,用户看到的是被翻译成其语言的回复,原文始终保留在旁。语言从第一条消息中检测得出,并固定到该工单上。每种语言的译文只计算一次并复用,因此长对话不会反复重新翻译。

时区

工单会记录用户提交时所在的时区。此后每条消息都会在您的时间旁边显示对方的当地时间,于是 14:32 发出的回复,对等待它的人来说会显示为实际的 02:32。当时区未知时,例如通过电子邮件到达的工单,或者时区与您相同时,则不显示任何内容。

Webhook

设置一个支持 Webhook URL,即可在工单事件发生时接收它们,用于触发告警或把工单镜像到您自己的系统中。投递内容带有签名,便于您验证它确实来自我们。

事件作用
support.ticket.created用户提交了一张工单。
support.ticket.message用户或您的某位操作员在对话中发布了一条消息。负载中的 fromStaff 会告诉您是哪一方。
support.ticket.status_changed工单状态发生了变化,例如变为已解决。
support.ticket.assigned工单被分配给了某位团队成员。

导入与迁移

将现有的身份系统迁移到您的 Authagonal 租户。支持两种来源 —— Duende IdentityServer(一个 SQL Server 数据库)和 Auth0(Management API)。每种来源都会运行只读预览,让您在提交之前准确查看将要复制的内容。

从 Duende IdentityServer 导入

将现有的 Duende IdentityServer SQL Server 数据库中的 Clients、Scopes、用户和角色迁移到您的 Authagonal 租户。导入分为两个阶段 —— 预览和提交 —— 这样您可以在任何更改之前查看将要复制的内容。

导入的内容

导入器从 Duende 的 ConfigurationDb 和 ASP.NET Identity 表中读取数据,将映射的行写入您的租户。短时效资源(如 persisted grants、device codes 和签名密钥)会被跳过。

实体源表备注
ClientsClients、ClientSecrets、ClientGrantTypes、ClientScopes、ClientRedirectUris已禁用的 Client 将以禁用状态导入。已过期的 secret 会被跳过。
ScopesApiScopes、ApiResources、IdentityResources可识别的用户声明映射会被保留。
用户AspNetUsers、AspNetUserClaims密码哈希(ASP.NET Identity V3)原样复制,首次登录时重新哈希。
角色AspNetRoles、AspNetUserRoles角色分配会被保留。
外部登录AspNetUserLogins仅作参考保存;导入后请通过 SSO 重新连接上游 IdP。

提交前预览

粘贴您的 Duende ConfigurationDb / IdentityDb 连接字符串,然后点击运行预览。预览会以只读方式连接并统计将要导入的每一行。不会写入任何数据。

  • Clients、Scopes、用户、角色和角色分配的实体计数。
  • 当目标租户已存在客户端(ClientId 匹配的会被覆盖)、角色或作用域(名称匹配的会被跳过)时发出冲突警告。
  • 对未知表和未映射列的警告,便于您了解哪些数据将被丢弃。
Import preview panel showing entity counts and warnings before committing the import

带有计数和警告的预览面板

密码哈希

Duende 使用 ASP.NET Identity V3(PBKDF2)存储密码。Authagonal 的 PasswordHasher 可直接验证该格式,并在首次成功登录时重新哈希为原生格式 —— 用户无需重置流程即可保留现有密码。

用户 ID 协调

如果此租户中已存在的用户与传入记录拥有相同的电子邮件,导入会在导入前将该账户的 userId 轮换为源 sub,这样导入的角色、登录和声明就会附加到现有账户上,而已经通过其源 sub 引用该用户的应用在切换后仍能继续解析。该账户现有的密码和个人资料会被保留;源角色会叠加合并到其上。预览会在您提交之前列出每一个将被协调的账户。

执行导入

查看预览后,点击开始导入。提交阶段会将客户端、作用域、用户、角色和外部登录引用写入您的租户存储。clientId 匹配的客户端会被覆盖;scope name、email 或 role name 匹配的行会被跳过,因此导入器可安全重复运行。

不会导入的内容

  • Persisted grants、device codes、server-side sessions —— 短时效,自动重新生成。
  • 签名密钥 —— Authagonal 为每个租户签发自己的密钥。
  • 自定义列和表 —— 任何超出 Duende 标准 schema 的内容都会以警告形式出现,以便您了解数据已被丢弃。
  • 已禁用的 Client —— 以禁用状态导入;准备好后可在 Clients 页面重新启用。

沙盒模式下不可用

导入仅对正式租户运行。导入前请退出沙盒模式。

从 Auth0 导入

将 Authagonal 连接到您的 Auth0 租户的 Management API,迁移您的应用、API、角色、用户和企业连接。导入的用户和应用 ID 会被保留,因此切换之后现有的 sub 和 client_id 引用仍可正常解析。

您需要准备的内容

在 Auth0 中创建一个授权访问 Management API 的机器到机器(Machine-to-Machine)应用,并授予以下读取 scope:read:users、read:clients、read:resource_servers、read:roles、read:connections、read:client_grants。将其域名、client ID 和 client secret 粘贴到导入表单中 —— 它们仅用于本次导入。

导入的内容

实体源表备注
应用clients、client-grants自动检测 public 与 confidential。client secrets 会被重新哈希,以便继续可用。
API 与 scopesresource-serversaudiences 和 scopes 会根据各 client 的 grants 分配给它们。
角色roles + 分配关系保留每个用户的角色分配关系。
用户users + identities档案和元数据会一并迁移;社交/企业身份将成为已关联的登录方式。
连接connections(OIDC)企业 OIDC 连接会成为联合提供方。SAML、社交和数据库连接会被跳过并给出警告。

密码

Auth0 的 Management API 从不返回密码哈希。如果您拥有 Auth0 支持团队协助导出的批量密码(NDJSON),请一并提供 —— bcrypt 哈希会原样导入,您的用户无需重置即可保留密码。该文件还包含您的完整用户集合,从而突破 Auth0 API 列表 1,000 个用户的上限。若不提供,用户将以档案形式导入,并在首次登录时设置新密码。

相同的预览、轮换与限制

上文所述的预览、owner-userId 轮换、可重复执行的提交以及沙盒限制,同样适用于 Auth0 导入。

API 参考

每个租户在 https://{slug}.authagonal.io 上暴露一个符合标准的 OIDC 服务器。所有端点遵循 OAuth 2.0 和 OpenID Connect 规范。此参考涵盖您的应用可能需要交互的每个端点。

OIDC Authorization Code Flow with PKCE: sequence diagram showing the 9-step flow between your app, the browser, and Authagonal

带 PKCE 的授权码流程

OIDC 发现和 JWKS

发现文档让 OIDC 客户端库可以自动配置自身。这两个端点均不需要身份验证。

GET /.well-known/openid-configuration

返回 OpenID Provider 配置文档。响应包含客户端与此租户交互所需的所有元数据。

字段描述
issuer租户发行者 URL
authorization_endpoint授权请求 URL
token_endpoint令牌交换 URL
userinfo_endpoint获取用户声明的 URL
jwks_uriJSON Web Key Set URL
revocation_endpoint令牌撤销 URL
introspection_endpoint令牌内省 URL
end_session_endpoint注销 / 结束会话 URL
device_authorization_endpoint设备授权请求 URL
pushed_authorization_request_endpoint推送授权请求端点的 URL (RFC 9126)。
require_pushed_authorization_requests租户是否在全局要求 PAR。即使此值为 false,单个客户端仍可以设置 RequirePushedAuthorizationRequests = true。
scopes_supported支持的作用域列表
response_types_supported支持的响应类型
grant_types_supported支持的授权类型
code_challenge_methods_supported支持的 PKCE 方法(S256)
backchannel_logout_supported是否支持后通道注销

GET /.well-known/openid-configuration/jwks

返回用于验证令牌签名的 JSON Web Key Set。响应包含一个由 EC P-256 公钥组成的 keys 数组(令牌使用 ES256 签名),每个公钥包含 kty(EC)、use、kid、alg、crv、x 和 y 字段。

Fetch discovery document
curl https://acme.authagonal.io/.well-known/openid-configuration

授权端点

GET /connect/authorize

发起授权码流程。用户必须有活跃会话,否则将被重定向到登录页面。成功后,用户将携带授权码被重定向回您的应用。

参数必需描述
response_type是必须为 "code"
client_id是您已注册的客户端标识符
redirect_uri是必须与已注册的重定向 URI 完全匹配
scope是以空格分隔的作用域列表(例如 "openid profile email")
state推荐用于 CSRF 防护的不透明值,在重定向中原样返回
code_challengePKCE 时必需code_verifier 的 SHA-256 哈希值经 Base64url 编码
code_challenge_methodPKCE 时必需必须为 "S256"
nonce可选绑定到 ID 令牌的值,用于防止重放攻击
login_hint可选预填充登录页面的电子邮箱字段

成功响应:302 重定向到 redirect_uri,附带 code 和 state 查询参数。

错误响应:302 重定向,附带 error、error_description 和 state 查询参数。

PKCE 必需

所有客户端默认要求 PKCE。生成一个 code_verifier(43 个或更多字符的随机字符串),使用 SHA-256 哈希,然后进行 Base64url 编码以创建 code_challenge。

推送授权请求 (PAR)

RFC 9126。客户端不再把每个授权参数放在 URL 上,而是用普通的客户端认证将它们 POST 到 /connect/par,并取回一个短期的不透明 request_uri。浏览器随后访问 /connect/authorize?client_id=...&request_uri=... — 其它内容不会出现在浏览器历史、服务器日志或 Referer 头中,并且服务器已在客户端认证下完成参数完整性校验。

POST /connect/par

客户端认证与 /connect/token 相同:使用 HTTP Basic 携带 client_id/client_secret,或者通过表单字段提交凭据。公共客户端无需 secret 直接提交。请求体携带的参数与你通常发送给 /connect/authorize 的相同;request_uri 本身会被拒绝(规范 §2.1 禁止链式 PAR)。返回 201 Created。

参数必需描述
client_id是你的客户端 ID。必须与已认证的客户端一致。
client_secret机密客户端你的客户端密钥。机密客户端必填。
response_type是必须为 "code"
redirect_uri是必须与已注册的重定向 URI 完全匹配
scope是以空格分隔的作用域列表(例如 "openid profile email")
code_challengePKCE 时必需code_verifier 的 SHA-256 哈希值经 Base64url 编码
code_challenge_methodPKCE 时必需必须为 "S256"
state推荐用于 CSRF 防护的不透明值,在重定向中原样返回
nonce可选绑定到 ID 令牌的值,用于防止重放攻击

响应

字段描述
request_uri一次性不透明引用,例如 <code>urn:ietf:params:oauth:request_uri:abc123…</code>。将其作为 <code>request_uri</code> 传给 <code>/connect/authorize</code>。
expires_in<code>request_uri</code> 的存活时间(秒)。默认为 90 — 与典型参考 IdP 一致。

在后续的 GET /connect/authorize?client_id=…&request_uri=… 调用中,所有其它参数都从已推送的载荷中读取,多余的查询参数会被忽略。授权调用上的 client_id 必须与推送请求时的客户端一致。一旦被消费(或 expires_in 到期),request_uri 会从存储中移除。

按客户端强制 PAR

在客户端上开启要求推送授权请求(门户,客户端,打开该客户端,安全选项卡),即可拒绝该客户端发起的普通 /connect/authorize 调用。对高风险客户端,推荐的做法是将 RequirePushedAuthorizationRequests = true 与 PKCE 组合使用,这样就彻底消除了 URL 栏作为攻击面。
Push an authorization request and follow up
# 1. Push parameters (server returns request_uri + expires_in)
curl -X POST https://acme.authagonal.io/connect/par \
  -u "my-app:CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "response_type=code" \
  -d "redirect_uri=https://app.example.com/callback" \
  -d "scope=openid profile email" \
  -d "state=$(openssl rand -hex 16)" \
  -d "code_challenge=YOUR_CODE_CHALLENGE" \
  -d "code_challenge_method=S256"

# 2. Send the user to /authorize with only client_id + request_uri
# https://acme.authagonal.io/connect/authorize?client_id=my-app&request_uri=urn:ietf:params:oauth:request_uri:abc123...

令牌端点

POST /connect/token

用凭证交换令牌。请求必须使用 Content-Type: application/x-www-form-urlencoded。客户端身份验证可以通过 HTTP Basic auth(Authorization: Basic base64(client_id:client_secret))或表单正文参数(client_id + client_secret)提供。

授权码授权

参数必需描述
grant_type是"authorization_code"
code是来自重定向的授权码
redirect_uri是必须与授权请求中使用的 URI 匹配
code_verifierPKCE 时必需用于生成 code_challenge 的原始随机字符串
client_id是您的客户端标识符(如果未使用 Basic auth)
client_secret机密客户端您的客户端密钥(如果未使用 Basic auth)

刷新令牌授权

参数必需描述
grant_type是"refresh_token"
refresh_token是要交换的刷新令牌
client_id是您的客户端标识符
client_secret机密客户端您的客户端密钥

客户端凭证授权

参数必需描述
grant_type是"client_credentials"
client_id是您的客户端标识符
client_secret是您的客户端密钥
scope可选以空格分隔的请求作用域

设备代码授权

参数必需描述
grant_type是"urn:ietf:params:oauth:grant-type:device_code"
device_code是来自设备授权响应的设备代码
client_id是您的客户端标识符
client_secret机密客户端您的客户端密钥

令牌响应:

字段描述
access_token用于 API 调用的访问令牌
token_type"Bearer"
expires_in令牌有效期(秒)
id_tokenOpenID Connect ID 令牌(请求 openid 作用域时)
refresh_token刷新令牌(授予 offline_access 作用域时)
Exchange authorization code with PKCE
curl -X POST https://acme.authagonal.io/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTHORIZATION_CODE" \
  -d "redirect_uri=https://app.example.com/callback" \
  -d "client_id=my-app" \
  -d "code_verifier=YOUR_CODE_VERIFIER"

UserInfo 端点

GET /connect/userinfo

返回已验证用户的声明。需要具有 openid 作用域的有效访问令牌。

字段类型描述
substring唯一用户标识符
emailstring用户电子邮箱地址
email_verifiedboolean电子邮箱是否已验证
given_namestring名字
family_namestring姓氏
namestring完整显示名称
phone_numberstring电话号码(如已提供)。在 <code>phone</code> 作用域下发出。
org_idstring该用户所属的组织。由你自己的预配应用分配(见预配应用一节),或用 PUT /api/v1/users/{userId} 设置;Authagonal 从不推导它。在 profile scope 下发放,用户没有组织时不出现。
rolesstring[]已分配角色的数组。仅当令牌携带 <code>roles</code> 作用域时才会发出。
groupsobject[]组成员关系数组,每项包含 id 和 name。仅当令牌携带 <code>groups</code> 作用域时才会发出。
Fetch user info
curl https://acme.authagonal.io/connect/userinfo \
  -H "Authorization: Bearer ACCESS_TOKEN"

令牌内省(RFC 7662)

POST /connect/introspect

验证令牌并返回其元数据。需要客户端凭证(Basic auth 或表单正文参数)。

参数必需描述
token是要内省的令牌
token_type_hint可选关于令牌类型的提示(例如 "refresh_token")

活跃令牌响应:

字段描述
activetrue
sub主体(用户 ID)
client_id令牌签发给的客户端
scope已授予的以空格分隔的作用域
iss发行者
exp过期时间(Unix 时间戳)
iat签发时间(Unix 时间戳)
aud受众
token_type令牌类型(例如 "Bearer")

非活跃令牌响应:{ "active": false }

始终返回 200 OK

根据 RFC 7662,内省端点对任何令牌都返回 200 OK,因此无法用它来枚举令牌。无效、已过期或已撤销的令牌只会返回 active: false。唯一的例外是调用方本身:未通过身份验证的客户端会收到 401 invalid_client。
Introspect a token
curl -X POST https://acme.authagonal.io/connect/introspect \
  -u "my-app:CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=ACCESS_OR_REFRESH_TOKEN"

令牌撤销(RFC 7009)

POST /connect/revocation

撤销先前签发的令牌。需要客户端凭证。

参数必需描述
token是要撤销的令牌
token_type_hint可选关于令牌类型的提示(例如 "refresh_token")

根据 RFC 7009 规范,该端点始终返回 200 OK,即使令牌无效或已被撤销。

访问令牌和刷新令牌

刷新令牌和访问令牌都可以被撤销。撤销刷新令牌时,也会撤销由其签发的访问令牌。已撤销的访问令牌会被内省端点和 userinfo 拒绝,但在本地验证 JWT 的资源服务器会继续接受它直到过期,因此请保持较短的访问令牌有效期。

设备授权(RFC 8628)

POST /connect/deviceauthorization

为输入受限设备(CLI、智能电视、IoT 设备)发起设备授权流程。设备向用户显示一个代码,用户随后在另一台有浏览器的设备上批准请求。

参数必需描述
client_id是您的客户端标识符
client_secret机密客户端您的客户端密钥
scope可选以空格分隔的作用域(默认为 "openid")

响应:

字段描述
device_code设备验证码(用于轮询)
user_code面向用户的代码,格式为 XXXX-XXXX
verification_uri用户访问以输入代码的 URL
verification_uri_complete预填充了 user_code 的 URL
expires_in默认为 300(秒,即代码有效期为 5 分钟)。按客户端设置。
interval5(秒——最小轮询间隔)

批准流程:用户访问 verification_uri,输入 user_code,然后批准请求。同时,设备使用 device_code 轮询令牌端点。

轮询错误代码:

错误含义
authorization_pending用户尚未批准——继续轮询
expired_token设备代码已过期——重新开始流程
access_denied用户拒绝了授权请求
Request device authorization
curl -X POST https://acme.authagonal.io/connect/deviceauthorization \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=my-cli" \
  -d "scope=openid profile email"

结束会话 / 注销

GET POST /connect/endsession

注销当前用户会话,通知每个注册了后通道或前通道注销 URI 的客户端,并撤销绑定到该会话的授权。如果没有与当前会话匹配的 id_token_hint,会先请用户确认注销。

参数必需描述
id_token_hint可选ID 令牌——用于验证 post_logout_redirect_uri
post_logout_redirect_uri可选注销后重定向的目标地址(必须已注册)
state可选在重定向中返回的不透明值

如果提供了有效的 post_logout_redirect_uri 且匹配已注册的 URI,用户将收到 302 重定向。否则,将返回 JSON 响应确认会话已结束。

后通道注销

当用户注销时,Authagonal 向每个客户端的 BackChannelLogoutUri 发送签名 JWT。JWT 包含 sub、aud、iss 和 http://schemas.openid.net/event/backchannel-logout 事件声明。您的应用应在收到此通知时使用户的本地会话失效。

SCIM 2.0 API 参考

Authagonal 支持 SCIM 2.0 协议,用于自动化用户和组的配置。Okta、Azure AD 和 OneLogin 等身份提供商可以使用此 API 将您的 Authagonal 租户与企业目录保持同步。

基础 URL:https://{slug}.authagonal.io/scim/v2

身份验证:所有请求需要 Bearer 令牌。在门户的SCIM页面生成 SCIM 令牌:选择您的 IdP 为其配置用户的客户端,然后点击创建令牌。

通用请求头:

请求头值
AuthorizationBearer SCIM_TOKEN
Content-Typeapplication/scim+json

列表端点接受 count(默认 100,最大 200;0 仅返回总数),并通过 filter 参数支持筛选(例如 userName eq "[email protected]")。用户使用 cursor 分页:传入上一个响应的 nextCursor。组可以使用 startIndex(从 1 开始)或 cursor。

用户

GET /scim/v2/Users: 列出用户,支持可选的分页和筛选。

查询参数描述
startIndex对用户仅接受 1;请改用 cursor 分页。更大的值会返回 400 invalidValue。
cursor不透明的分页游标:传入上一页的 nextCursor 以获取下一页
count每页最大结果数(默认:100,最大:200;0 仅返回总数)
filterSCIM 筛选表达式(例如 userName eq "[email protected]")

GET /scim/v2/Users/{id}: 通过 Authagonal 用户 ID 获取单个用户。

POST /scim/v2/Users: 创建新用户。返回 201 Created。

字段必需描述
userName是电子邮箱地址(在租户内必须唯一)
name.givenName否名字
name.familyName否姓氏
displayName否完整显示名称
active否用户是否处于活跃状态(默认:true)
externalId否来自上游身份提供商的标识符

PUT /scim/v2/Users/{id}: 完全替换用户资源。必须提供所有字段。

PATCH /scim/v2/Users/{id}: 使用 SCIM PatchOp 进行部分更新。

操作支持的路径示例值
replaceuserName, active, name.givenName, name.familyName, displayName, externalId, preferredLanguagetrue / false,或字符串值
adduserName, active, name.givenName, name.familyName, displayName, externalId, preferredLanguagetrue / false,或字符串值
removename.givenName, name.familyName, displayName, externalId, preferredLanguage(无需值)

DELETE /scim/v2/Users/{id}: 软删除用户(停用账户并撤销所有令牌)。返回 204 No Content。

Create a user via SCIM
curl -X POST https://acme.authagonal.io/scim/v2/Users \
  -H "Authorization: Bearer SCIM_TOKEN" \
  -H "Content-Type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
    "userName": "[email protected]",
    "name": {
      "givenName": "Jane",
      "familyName": "Smith"
    },
    "displayName": "Jane Smith",
    "active": true,
    "externalId": "ext-12345"
  }'

组

GET /scim/v2/Groups: 列出所有组,支持可选的分页和筛选。

GET /scim/v2/Groups/{id}: 通过 ID 获取单个组,包括其成员列表。

POST /scim/v2/Groups: 创建新组。返回 201 Created。

字段必需描述
displayName是组显示名称
members否成员对象数组,每个对象包含一个 value 字段(用户 ID)
externalId否来自上游身份提供商的标识符

PUT /scim/v2/Groups/{id}: 完全替换组资源(包括其成员列表)。

PATCH /scim/v2/Groups/{id}: 部分更新:添加、移除或替换成员,或更改 displayName 和 externalId。

DELETE /scim/v2/Groups/{id}: 硬删除组。返回 204 No Content。

Add members to a group via PATCH
curl -X PATCH https://acme.authagonal.io/scim/v2/Groups/GROUP_ID \
  -H "Authorization: Bearer SCIM_TOKEN" \
  -H "Content-Type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
    "Operations": [
      {
        "op": "add",
        "path": "members",
        "value": [
          { "value": "USER_ID_1" },
          { "value": "USER_ID_2" }
        ]
      }
    ]
  }'

SCIM 错误响应

当 SCIM 请求失败时,响应正文遵循 SCIM 错误模式:{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "status": "400", "detail": "..." }。常见状态码包括 400(错误请求)、404(资源未找到)、409(冲突 / 重复)和 429(频率限制)。

Portal API(自动化)

Portal API 让您自己的后端能够自动化您在门户中所能执行的一切操作——管理用户、客户端、组、角色、作用域、SSO 连接和设置——通过一种机器对机器(M2M)凭据完成。它与门户 UI 调用的是同一套 API。

基础 URL:https://portal-api.<your-domain>/api/v1。请求通过 Bearer 访问令牌进行身份验证;租户取自令牌,而非 URL。

创建 API 凭据

在门户中,打开 Clients → Create API credential,选择一个访问级别并为其命名。Authagonal 会生成一个为 Portal API 配置好的 OAuth client_credentials 客户端,并返回 Client ID 和密钥。

立即复制密钥

客户端密钥仅显示一次,就在创建之后。请在关闭对话框前将其保存到您的密钥管理器中——如果丢失,请删除该凭据并重新创建一个。

访问级别

作用域授予权限
tenant:owner完全访问,包括破坏性的、仅限所有者的操作,例如删除整个租户。
tenant:admin管理除仅限所有者操作之外的一切——用户、客户端、SSO、组、角色、品牌和设置。
tenant:developer管理客户端、作用域、品牌和配置应用。
tenant:support为支持任务读取和管理用户,并读取审计日志。

您只能授予自己拥有的权限

凭据的权限不能高于创建它的人。管理员无法签发所有者级别的凭据,且平台管理作用域永远无法授予凭据。

获取令牌

在您的管理租户令牌端点——https://<your-tenant>.<your-domain>/connect/token——用该凭据换取访问令牌,然后将令牌作为 Bearer 请求头发送给 Portal API。令牌有效期为一小时。

获取令牌,然后调用 API
# 1. Exchange the credential for an access token (your tenant's token endpoint)
curl -X POST https://acme.authagonal.io/connect/token \
  -d grant_type=client_credentials \
  -d client_id=api-3f2a... \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d scope=tenant:admin

# Response: { "access_token": "ey...", "token_type": "Bearer", "expires_in": 3600 }

# 2. Call the Portal API with the access token
curl https://portal-api.authagonal.io/api/v1/users \
  -H "Authorization: Bearer $ACCESS_TOKEN"

端点

所有路径均相对于基础 URL,并需要 Bearer 访问令牌。每个分组旁的作用域是该凭据所需的最低访问级别。分页方式因资源而异:用户和审计日志使用 count(1 到 200,默认 50)和 after(设为上一个响应的 continuationToken);组使用 startIndex 和 count;组织使用 limit 和 cursor;客户端、角色和作用域返回所有行。

客户端tenant:developer

GET/api/v1/clients列出 OAuth 客户端。

GET/api/v1/clients/{id}按 ID 获取单个客户端。

POST/api/v1/clients创建客户端。返回 201 和客户端记录。此处不返回密钥:请使用 POST /api/v1/clients/{clientId}/secrets 生成一个,该密钥只显示一次。

PUT/api/v1/clients/{id}更新客户端(重定向 URI、授权类型、令牌有效期、PKCE/PAR 要求)。

DELETE/api/v1/clients/{id}删除客户端。

POST/api/v1/clients/api-credential创建机器对机器的 Portal API 凭据。

用户tenant:support

GET/api/v1/users列出用户。支持 count、search(邮箱或姓名前缀)、organizationId(筛选出单个组织)以及 after(游标分页)。

GET/api/v1/users/count租户的用户总数。

GET/api/v1/users/stats/mfaMFA 注册统计。

GET/api/v1/users/{id}获取单个用户。

POST/api/v1/users/invite通过电子邮件邀请用户。创建一个待激活账户,并通过电子邮件向受邀者发送链接,供其自行设置密码。可选的 organizationId 和 organizationRoles 还会添加组织成员关系。

PUT/api/v1/users/{id}更新用户(资料、email、isActive、emailConfirmed、organizationId)。更改 email 或 emailConfirmed 需要 tenant:admin。

DELETE/api/v1/users/{id}删除用户。

GET/api/v1/users/{id}/mfaGet a user's enrolled MFA methods.

DELETE/api/v1/users/{id}/mfaReset a user's MFA enrollment. Needs tenant:admin.

角色tenant:admin

GET/api/v1/roles列出角色。

POST/api/v1/roles创建角色。

DELETE/api/v1/roles/{id}删除角色。

POST/api/v1/roles/assign为用户分配角色。

POST/api/v1/roles/unassign移除用户的角色。

组tenant:admin

GET/api/v1/groups列出组。

GET/api/v1/groups/{id}获取组及其成员。

POST/api/v1/groups创建组。

POST/api/v1/groups/{id}/members向组中添加成员。

DELETE/api/v1/groups/{groupId}/members/{userId}从组中移除成员。

DELETE/api/v1/groups/{id}删除组。

GET/api/v1/group-role-mappings列出组到角色的映射(根据组成员身份在签发令牌时授予的角色)。

作用域tenant:developer

GET/api/v1/scopes列出 API 作用域。

POST/api/v1/scopes创建作用域。

DELETE/api/v1/scopes/{name}删除作用域。

SSO 连接tenant:admin

GET/api/v1/saml/connections列出 SAML 连接。

POST/api/v1/saml/connections创建 SAML 连接。

DELETE/api/v1/saml/connections/{id}删除 SAML 连接。

GET/api/v1/oidc/connections列出 OIDC 连接。

POST/api/v1/oidc/connections创建 OIDC 连接。

DELETE/api/v1/oidc/connections/{id}删除 OIDC 连接。

GET/api/v1/sso/domains列出路由到 SSO 连接的域名(home-realm 发现)。

品牌tenant:developer

GET/api/v1/branding获取租户品牌设置(颜色、logo、支持的语言)。

PUT/api/v1/branding更新租户品牌设置。

设置tenant:admin

GET/api/v1/settings获取租户设置(webhook、公开注册、令牌策略)。

PUT/api/v1/settings更新租户设置。

POST/api/v1/settings/webhook-secret/regenerate轮换 webhook 签名密钥。

POST/api/v1/settings/test-email使用当前邮件配置发送测试邮件。

自定义域名和邮件tenant:admin

GET/api/v1/custom-domains列出自定义登录域名及其验证状态。

POST/api/v1/custom-domains添加自定义域名。

POST/api/v1/custom-domains/{domain}/verify为自定义域名触发 DNS 验证。

DELETE/api/v1/custom-domains/{domain}移除自定义域名。

GET/api/v1/email/domains/{domainId}获取发件人邮件域名的 DNS 记录和验证状态。

审计日志tenant:support

GET/api/v1/audit查询租户审计日志。

通过 SCIM 预配用户

如需从 IdP(Entra、Okta)批量预配用户和组,请使用 SCIM 2.0 API,而不是这些端点。

示例:邀请用户

POST /api/v1/users/invite
curl -X POST https://portal-api.authagonal.io/api/v1/users/invite \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "firstName": "Ada",
    "lastName": "Lovelace"
  }'

# 200 OK
# { "userId": "8f3a...", "email": "[email protected]" }

UI 能做的一切

Portal API 公开了门户 UI 所使用的同一套端点,因此您在门户中能执行的任何操作都可以自动化——但受凭据访问级别的限制。

组织 API

以下所有路由都要求 TenantAdmin 策略,除非另有说明,均位于 /api/v1/organizations 之下。如何获取凭据请参阅门户 API。两个列表路由采用游标分页:传入 limit(默认 50,1 到 200)以及上一次的 nextCursor 作为 cursor;它们返回 { items, nextCursor },无效的游标会得到 400 invalid_cursor。组织对象带有只读的 domains 列表。

方法路径请求体说明
GET/api/v1/organizations–按 id 顺序返回一页组织。查询参数:cursor?、limit?(默认 50,1 到 200)。返回 { items, nextCursor };nextCursor 为 null 表示最后一页。
POST/api/v1/organizationsslug, name, brandingJson?, enabled?, requireMembershipForTokens?, allowAutoMembership?, metadata?201 + 该组织。409 slug_taken。
GET/api/v1/organizations/{idOrSlug}–先按 id 查找,再按 slug 查找。
PUT/api/v1/organizations/{id}name?, brandingJson?, slug?, enabled?, requireMembershipForTokens?, allowAutoMembership?, metadata?只更改指定的字段。不同的 slug 会被拒绝:slug 创建后不可变。domains 不能在此处更改。
DELETE/api/v1/organizations/{id}–先删除该组织的所有成员关系,再删除组织本身。
GET/api/v1/organizations/{id}/members–按用户 id 顺序返回一页成员关系,并从用户存储中解析成员当前的 email/name。查询参数:cursor?、limit?。
POST/api/v1/organizations/{id}/membersuserId? | email?, status?, roles?201 + 该成员关系。404 user_not_found。409 membership_exists。保留的 tenant:* 和 platform:* 角色会以 400 invalid_role 被拒绝,而不是被剔除。
PUT/api/v1/organizations/{id}/members/{userId}status?, roles?将 status 设为 active 时,如果 joinedAt 尚未设置,则会写入它。
DELETE/api/v1/organizations/{id}/members/{userId}–直接移除该记录,而不是更改状态。
POST/api/v1/organizations/{id}/domainsdomain声明一个电子邮件域名,未验证。201 + { domain, verified, verifiedAt, createdAt, recordName, recordValue }。400 domain_invalid。409 domain_exists、domain_taken。
POST/api/v1/organizations/{id}/domains/{domain}/verify–查询 recordName 处的 TXT 记录,并与 recordValue 完全匹配。验证通过时返回 200 + 该域名(重复调用是无操作的 200);未匹配返回 409 verification_failed;409 domain_taken;404 domain_not_found。
DELETE/api/v1/organizations/{id}/domains/{domain}–204。撤销该声明,无论是否已验证。现有成员保留。404 domain_not_found。
POST/api/v1/organizations/backfilldryRun? = true将旧版 AuthUser.organizationId 标签迁移为真实的 Organization/OrganizationMembership 记录。
GET/api/v1/users/{userId}/organizations–该用户所属的每个组织,以及其在各组织中的 status/roles。

登录页面

这些是您的终端用户在您租户的认证服务器上看到的托管页面。Authagonal 提供每一个开箱即用的页面,让您无需构建任何 UI 即可获得完整、安全的登录体验。本页将逐一介绍每个页面,并说明哪些门户设置可以控制它。

完全白标

这里的每个页面都由您租户的品牌设置渲染——您的 Logo、配色、应用名称和自定义 CSS。这些页面还遵循 prefers-color-scheme,会随用户设备在浅色与深色之间自动切换。

登录

Hosted sign-in screen with an email field, Continue button, single sign-on provider buttons, and forgot-password and create-account links
  • 邮箱优先的两步式流程:用户先输入邮箱并点击继续,然后出现密码输入框。
  • 当存在 SSO 连接时,「使用 {provider} 继续」单点登录按钮会自动出现。
  • 忘记密码和创建账户链接,二者均可显示或隐藏。
  • 可选的 Cloudflare Turnstile 人机验证,用于阻止自动化登录尝试。

在门户管理后台中控制

  • 品牌设置用于设定 Logo、配色、应用名称、支持邮箱和自定义 CSS。
  • 显示或隐藏忘记密码和注册链接(品牌设置)。
  • SSO 连接会添加社交登录按钮(SSO 页面)。
  • 会话有效期和锁定阈值(设置 → 会话)。

注册

Account registration screen with first and last name fields, email, password, and a live password-policy checklist
  • 收集名字和姓氏(可选)、邮箱以及密码。
  • 实时密码策略清单会随用户输入而更新,让要求在提交前一目了然。
  • 可选的 Cloudflare Turnstile 人机验证。
  • 为已有账户的用户提供「登录」链接。

在门户管理后台中控制

  • 显示或隐藏注册链接(品牌设置)。如需完全关闭注册,请关闭允许公开注册(设置 → 会话)。
  • 您租户的密码策略决定了清单内容。
  • 品牌设置为整个页面设定样式。

忘记密码

Forgot-password screen with an email field and a neutral check-your-email confirmation state
  • 用户输入邮箱后,会看到一条中性的「请查收邮件」确认提示。
  • 该页面绝不透露账户是否存在,从而挫败账户枚举探测。
  • 「返回登录」链接可将用户带回登录页面。

在门户管理后台中控制

  • 显示或隐藏忘记密码链接(品牌设置)。
  • 您租户的邮件投递负责发送重置邮件。
  • 品牌设置为整个页面设定样式。

重置密码

Reset-password screen with new and confirm password fields and a live per-rule requirement checklist
  • 新密码和确认密码输入框,配有逐条规则的实时要求清单。
  • 当重置令牌不再有效时,会显示清晰的链接无效或已过期状态。
  • 确认密码已更改的成功状态。

在门户管理后台中控制

  • 您租户的密码策略决定了清单内容。
  • 品牌设置为整个页面设定样式。

MFA 验证

MFA challenge screen with a method switcher, a six-digit authenticator code field, recovery-code entry, and a passkey button
  • 在身份验证器应用、通行密钥和恢复码之间切换的方式切换器。
  • 6 位 TOTP 输入框,所有数字输入完毕后自动提交。
  • 为丢失身份验证器访问权限的用户提供恢复码输入。
  • 用于硬件支持验证的通行密钥按钮。

在门户管理后台中控制

  • MFA 策略按应用设置(客户端 → 安全)。
  • 任何已注册验证因素的用户都会始终被要求验证,无论策略如何。

MFA 设置

MFA setup screen showing enrolled-method status, authenticator QR code and manual key, passkey enrolment, and recovery-code generation
  • 显示已注册方式的状态,让用户知道哪些已经配置好。
  • 通过二维码、手动密钥备用方案和确认步骤完成身份验证器设置。
  • 用于硬件支持认证的通行密钥注册。
  • 用于账户恢复的恢复码生成。
  • 当 MFA 为自助而非强制时,提供可选的跳过。

在门户管理后台中控制

  • MFA 策略按应用设置;强制会在登录时强制完成设置(客户端 → 安全)。
  • 品牌设置为整个页面设定样式。

设备授权

Device authorization screen with a centered user-code entry field, an Approve button, and an approved confirmation state
  • 居中的用户代码输入框,用于输入设备上显示的代码。
  • 授权该设备的批准步骤。
  • 当用户尚未认证时显示的登录过渡页。
  • 设备授权完成后的已批准确认。

在门户管理后台中控制

  • 在应用上启用设备代码授权(客户端 → 权限范围)。
  • 设置设备代码有效期(客户端 → 令牌)。
Consent screen showing the requesting application's logo and name, a per-scope permission list, and Allow and Deny buttons
  • 显示发起请求的客户端 Logo 和名称。
  • 逐项权限列表,为每项权限配有友好、易读的标签。
  • 用于授予或拒绝访问的允许和拒绝按钮。
  • 解释该决定含义的授权提示页脚。

在门户管理后台中控制

  • 按应用开启需要同意(客户端 → 常规)。
  • Logo、名称和 URL来自应用自身的元数据。
  • 品牌设置渲染授权同意卡片。

已连接应用(授权)

Connected apps screen listing the applications a user has authorized with their scopes and granted date, plus a revoke control
  • 列出用户已授权的每个应用,含其名称、权限范围和授权日期。
  • 撤销对某个应用的访问权限,生效前会有确认步骤。
  • 当用户尚未授权任何应用时,显示友好的空状态。

在门户管理后台中控制

  • 该列表由需要授权同意的应用填充。
  • 品牌设置为整个页面设定样式。

账户

位于 /login/account 的托管自助账户页面,已登录用户可在此管理自己的个人资料和首选语言,无需门户访问权限。

Self-service account screen with editable profile fields and a preferred Language selector
  • 编辑名字和姓氏、公司和电话;电子邮件地址以只读方式显示。
  • 从受支持的语言中选择首选语言;界面会即时预览所选语言,并在保存时保留该选择。
  • 已保存的语言决定用户的托管界面以及他们所收到的事务性电子邮件的语言。

在门户管理后台中控制

  • 品牌设置为整个页面设定样式。
  • 管理员也可在门户的用户页面上编辑同一个首选语言。

身份验证流程

身份验证流程涵盖终端用户如何与您的 Authagonal 租户交互——登录、注册、重置密码和设置 MFA。这些端点由托管登录页面使用,如果您正在构建自定义登录 UI,也可以直接调用。

登录

POST /api/auth/login

使用电子邮箱和密码验证用户身份。成功后,签发会话 Cookie 并返回用户配置文件。如果配置了 MFA,响应会指示在会话完全建立之前需要第二因素。

请求正文:

Login request
{
  "email": "[email protected]",
  "password": "correct-horse-battery-staple"
}

成功响应:

字段类型描述
userIdstring唯一用户标识符
emailstring用户电子邮箱地址
namestring完整显示名称
mfaAvailableboolean用户是否已注册 MFA 方法

MFA 必需响应:当用户已注册 MFA 时,响应包含 mfaRequired: true 以及 challengeId 和列出可用 MFA 方法的 methods 数组。

MFA 设置必需响应:当应用的 MFA 策略为“必须启用”但用户尚未注册时,响应包含 mfaSetupRequired: true 和用于注册流程的 setupToken。

错误响应:

错误代码HTTP 状态描述
invalid_credentials401电子邮箱或密码不正确
account_disabled403账户已被管理员停用
email_not_confirmed403用户尚未验证其电子邮箱地址
locked_out423账户已被临时锁定(包含以秒为单位的 retryAfter)。仅在密码正确时返回;锁定期间密码错误将返回 invalid_credentials
sso_required409电子邮箱域名已配置 SSO(包含 redirectUrl)
too_many_attempts429来自此 IP 或针对此电子邮箱的登录尝试次数过多;请稍后再试
captcha_failed400Turnstile 质询失败或缺失(仅在启用 Turnstile 时)

SSO 检查:如果用户的电子邮箱域名配置了 SSO 连接,登录端点将返回 sso_required 和 redirectUrl。客户端应将用户重定向到 SSO 提供商。

账户锁定:连续 maxFailedAttempts 次登录失败后,账户将被锁定 lockoutDurationMinutes。这两个值均可在租户设置中配置。

托管登录页面

登录端点通常由托管登录页面调用,而非直接由您的应用调用。使用 OIDC 授权码流程发起身份验证——您的用户将自动被重定向到托管登录页面。

注册

POST /api/auth/register

创建新用户账户并发送验证邮件。当<strong>Require verified email to sign in</strong>(设置 &rarr; 会话)处于开启状态时(这是默认设置),用户必须先验证其电子邮箱才能登录。

请求正文:

Registration request
{
  "email": "[email protected]",
  "password": "a-strong-password-here",
  "firstName": "Jane",
  "lastName": "Smith"
}
字段必需描述
email是电子邮箱地址(必须唯一)
password是必须符合租户密码策略
firstName否名字
lastName否姓氏

成功:201 Created,返回新账户的 userId。使用已注册的邮箱注册同样返回 201:我们绝不透露某个邮箱是否已存在(以防止账户枚举),而是改为通过邮件通知真正的账户持有者。

错误响应:

错误代码HTTP 状态描述
weak_password400密码不符合租户密码策略
rate_limited429注册尝试次数过多
provisioning_rejected422配置 Webhook 拒绝了该注册
invalid_email400电子邮箱地址无效
captcha_failed400Turnstile 质询失败或缺失(仅在启用 Turnstile 时)
public_signup_disabled403此租户已关闭公开注册(设置 &rarr; 会话)

密码策略

在提交前通过 GET /api/auth/password-policy 检查租户的密码要求。它会返回一个 rules 列表:最小长度和所需的字符类别。

密码重置

POST /api/auth/forgot-password

请求密码重置邮件。无论电子邮箱是否存在,端点始终返回成功响应,以防止电子邮箱枚举。

Forgot password request
{
  "email": "[email protected]"
}

POST /api/auth/reset-password

使用邮件链接中的令牌重置用户密码。

Reset password request
{
  "token": "RESET_TOKEN_FROM_EMAIL",
  "newPassword": "new-strong-password"
}

成功密码重置的副作用:

  • 失败登录尝试计数器重置为零
  • 所有现有刷新令牌被撤销
  • 生成新的安全戳(使所有现有会话失效)

MFA 设置与验证

Authagonal 支持三种 MFA 方法:TOTP(身份验证器应用)、WebAuthn(安全密钥和生物识别)和一次性恢复代码。

TOTP 设置

POST /api/auth/mfa/totp/setup: 返回 <code>setupToken</code>、QR 码数据 URI 和手动输入密钥。用户使用身份验证器应用(Google Authenticator、Authy、1Password 等)扫描 QR 码,然后确认注册。

POST /api/auth/mfa/totp/confirm: 通过发送设置步骤返回的 <code>setupToken</code> 以及身份验证器应用中的 6 位数代码来确认 TOTP 注册。

Confirm TOTP enrollment
{
  "setupToken": "SETUP_TOKEN_FROM_SETUP_RESPONSE",
  "code": "123456"
}

WebAuthn 设置

POST /api/auth/mfa/webauthn/setup——返回 WebAuthn API 的凭证创建选项。浏览器使用这些选项调用 navigator.credentials.create()。

POST /api/auth/mfa/webauthn/confirm: 通过提交浏览器的证明响应来确认 WebAuthn 注册。

恢复代码

POST /api/auth/mfa/recovery/generate: 生成 10 个一次性的 10 字符恢复代码(格式为 <code>XXXXX-XXXXX</code>)。每个代码只能使用一次以绕过 MFA。必须先注册身份验证器应用或通行密钥。

恢复代码仅显示一次

恢复代码仅在生成时显示,之后无法再次获取。如果用户同时丢失了身份验证器设备和恢复代码,管理员必须在门户中手动删除其 MFA 凭证后用户才能再次登录。

MFA 验证

POST /api/auth/mfa/verify: 在成功密码登录后完成 MFA 挑战。

字段必需描述
challengeId是来自登录响应的挑战 ID
method是"totp"、"recovery" 或 "webauthn"
codeTOTP / 恢复代码6 位 TOTP 代码或恢复代码(XXXXX-XXXXX)
assertionWebAuthn来自 navigator.credentials.get() 的断言响应

MFA 状态

GET /api/auth/mfa/status: 返回用户当前已注册的 MFA 方法。

SSO 登录流程

Authagonal 支持 SAML 2.0 和基于 OIDC 的 SSO 连接。基于域的路由根据用户的电子邮箱地址自动检测使用哪个 SSO 提供商。

SSO 检查

GET /api/auth/[email protected]

字段类型描述
ssoRequiredboolean该电子邮箱域名是否需要 SSO
providerTypestring"saml" 或 "oidc"
connectionIdstringSSO 连接标识符
redirectUrlstring将用户重定向到 SSO 登录的 URL

SAML 流程

用户被重定向到 GET /saml/{connectionId}/login,该端点向身份提供商发送 SAML AuthnRequest。身份提供商验证用户身份后将 SAML 响应发回 Assertion Consumer Service (ACS) 端点。Authagonal 验证断言、创建或更新用户并签发会话 Cookie。

用于配置身份提供商的 SAML 元数据可在 GET /saml/{connectionId}/metadata 获取。

OIDC 流程

用户被重定向到 GET /oidc/{connectionId}/login,该端点使用 PKCE 重定向到上游身份提供商。用户完成身份验证后,回调端点 /oidc/callback 交换授权码、验证 ID 令牌并创建或更新用户。

JIT 配置:SAML 和 OIDC 流程均支持即时配置,该功能在每个连接上默认关闭(启用 JIT 开通)。开启后,如果用户在租户中不存在,将根据身份提供商的声明自动创建。如果用户已存在,其配置文件属性将更新为与提供商的最新值匹配。

基于域的路由

基于域的路由意味着您的用户无需知道他们使用的是哪个 SSO 提供商。只需输入电子邮箱地址即可——Authagonal 会将域名匹配到正确的 SSO 连接并自动重定向。

Backend-for-Frontend(BFF)

BFF 让 OAuth 令牌完全不进入浏览器。您的单页应用只持有一个 httpOnly 会话 Cookie,由您自己后端上的机密客户端完成 OpenID Connect 流程,并在服务端持有令牌。

单页应用能读到的任何东西,跨站脚本都能偷走,其中就包括内存里的访问令牌和 localStorage 里的刷新令牌。把令牌存在浏览器中还会压低它们的有效期,因为脚本触手可及的长期刷新令牌始终是一项悬着的风险。IETF 的 OAuth 2.0 for Browser-Based Apps 当前最佳实践,正是出于这个原因推荐这一模式。

作为交换,您得到的是:一个不出现任何令牌、却能跨页面刷新继续存在的会话;由服务端替您完成的令牌刷新;通过后通道注销实现的即时撤销;以及一个已认证的代理,让您的 API 永远不必解析一个浏览器有可能篡改过的令牌。

创建客户端

在门户中打开客户端,选择创建 BFF 应用。填入您的应用对外提供服务的基础 URL,例如 https://app.acme.com,Authagonal 会为您注册一个配置正确的机密客户端,而不是把拼装的活儿丢给您:

设置项值
重定向 URI{appBaseUrl}/bff/callback
注销后重定向 URI{appBaseUrl}/
后通道注销 URI{appBaseUrl}/bff/backchannel-logout
授权类型authorization_code, refresh_token
作用域openid, profile, email, offline_access
PKCE 与客户端密钥两者均为必需

密钥只显示一次

响应中携带 clientId、clientSecret 和 authority,之后密钥再也无法找回,因为我们只保存它的哈希值。请把它直接放进后端的配置或密钥存储中。如果丢失了,请另建一个客户端,不要试图找回这一个。

接入您的后端

支持两种运行时,共享同一套核心协议:面向 .NET 的 Authagonal.Bff 和面向 Node 的 @authagonal/bff,后者带有 Express 和 Next.js 适配器。把其中任意一个指向门户刚刚给出的那些值即可。Node 包还需要您自己提供一个 cookieSecret,用于加密其 Cookie。

.NET
// dotnet add package Authagonal.Bff
builder.Services.AddAuthagonalBff(o =>
{
    o.Authority    = "https://acme.authagonal.io";       // your tenant auth host
    o.ClientId     = builder.Configuration["Bff:ClientId"]!;
    o.ClientSecret = builder.Configuration["Bff:ClientSecret"]!;
    o.Scope        = ["openid", "profile", "email", "offline_access"];
    o.PostLogoutRedirectUri = "https://app.acme.com/";
});

// Trust X-Forwarded-Proto from your ingress. With no options, UseForwardedHeaders() changes nothing.
builder.Services.Configure<ForwardedHeadersOptions>(o =>
{
    o.ForwardedHeaders = ForwardedHeaders.XForwardedProto;
    o.KnownNetworks.Clear();
    o.KnownProxies.Clear();
});

var app = builder.Build();

app.UseForwardedHeaders();   // required behind a proxy or ingress, see the note below
app.MapAuthagonalBff();
app.MapFallbackToFile("index.html");   // your SPA
app.Run();
Node(Express)
// npm install @authagonal/bff
import express from 'express';
import { authagonalBff } from '@authagonal/bff/express';

const app = express();

app.use(authagonalBff({
  authority: 'https://acme.authagonal.io',
  clientId: process.env.BFF_CLIENT_ID,
  clientSecret: process.env.BFF_CLIENT_SECRET,
  cookieSecret: process.env.BFF_COOKIE_SECRET,   // encrypts the session and login cookies
  scope: ['openid', 'profile', 'email', 'offline_access'],
  postLogoutRedirectUri: 'https://app.acme.com/',
}));

app.listen(8080);

位于代理之后时,请信任转发标头

几乎每种部署都会把 BFF 放在 ingress 或负载均衡器之后,由它终结 TLS,并以明文 HTTP 与您的进程通信。如果不处理转发标头,BFF 会认为请求是不安全的,于是签发的 __Host- 会话 Cookie 不带 Secure 属性,浏览器随后会悄悄丢弃它。症状是登录干净利落地完成,会话却始终不出现。在 .NET 中,请在 ForwardedHeadersOptions 中启用 XForwardedProto,并在 MapAuthagonalBff() 之前调用 app.UseForwardedHeaders(),如上所示:不加配置的裸调用不会信任任何标头。Node 适配器会自行读取 X-Forwarded-Proto,并且始终将 __Host- Cookie 标记为 Secure;请设置框架的 trust-proxy 选项,使转发给您 API 的客户端地址是真实地址。

端点

默认挂载在 /bff 之下。它们必须由与您 SPA 相同的来源提供服务,因为会话 Cookie 是 httpOnly 且同源的:请把它们放在同一个主机名之下,而不是放在单独的 API 域名上。

路由用途
GET /bff/login?returnUrl=/发起登录并重定向到 Authagonal。随后把用户送回 returnUrl。
GET /bff/callbackOIDC 重定向 URI。由 BFF 代为处理,您无需自己编写。
GET /bff/user返回 isAuthenticated、会话声明和 sessionExpiresAt。需要携带防伪标头。
GET|POST /bff/logout在本地和 Authagonal 侧同时结束会话。
POST /bff/backchannel-logout接收来自 Authagonal 的注销通知,因此在别处退出登录也会结束此处的会话。

在浏览器中调用

每个非导航请求都必须携带一个固定的防伪标头。它与 Cookie 的 SameSite 属性一起防御跨站请求伪造:跨站的表单提交无法设置自定义标头,因此缺少该标头的请求会被拒绝。

查询当前登录的是谁
const me = await fetch('/bff/user', {
  headers: { 'X-Authagonal-Bff': '1' },
}).then(r => r.json());

if (!me.isAuthenticated) {
  // Navigate, do not fetch: this is a redirect to your identity provider.
  window.location.href = '/bff/login?returnUrl=' + encodeURIComponent(location.pathname);
}

登录和注销请通过页面导航完成,而不是 fetch:location.href = '/bff/login'。这些路由返回的是跳转到您身份提供商的重定向,而这样的重定向链并不是 fetch 能够有效跟随的。

调用您的 API

BFF 可以在自己的基础路径下转发您的 API,并在转发途中附上会话的访问令牌。浏览器发送的是 Cookie,您的 API 收到的是一个按常规校验的 Bearer 令牌,而该令牌的任何部分对页面既不可见,也无法伪造。注册一个上游后,发往 /bff/api/** 的请求就会以已认证的身份到达它。把列表留空,代理则被完全禁用。

转发一个上游 API
o.Upstreams.Add(new BffUpstream
{
    Prefix        = "/orders",                        // matches /bff/api/orders/**
    TargetBaseUrl = "https://api.internal.acme.com",
    StripPrefix   = false,                            // keep /orders in the forwarded path
});
选项默认值作用
Upstreams[]代理转发到的那些 API。留空则禁用代理端点。
Prefix/该上游负责处理的、位于 /bff/api 之后的路径前缀,例如 /orders。
TargetBaseUrl-请求被转发到的基础 URL。
StripPrefixfalse转发前去掉匹配到的前缀。这样您可以用一个人为设定的路由前缀,把一个 BFF 分发到共用同一路径命名空间的多个后端。
AllowAnonymousProxyRequestsfalse当请求没有可用会话时,不再拒绝它,而是在不带 Authorization 标头的情况下转发。适用于同时服务已登录调用方和匿名调用方的 API。
RequiredAuthority[]以 type:action 对给出的权限门槛,例如 email:send。设置后,代理会在转发前检查外发令牌的 RFC 9396 authorization details。
AuthorityLocation-当权限是针对某个公开资源标识符授予的,而该标识符与代理实际调用的内部地址不同时,此上游对外所用的 RFC 9396 locations 根地址。
StrictAuthorityfalse当调用带有代理无法判定的授权约束时,拒绝该调用而不是转发它。代理是盲转发的,不会推导出任何约束上下文,因此该选项默认关闭。
ExchangeRoutes[]这些代理路由在调用上游时使用的是绑定了上下文的交换令牌,而不是会话的主访问令牌。第一个匹配到的模式生效。

WebSocket

WebSocket 握手既无法携带自定义标头,也无法携带 Bearer 令牌,因此防伪标头和代理在这里都帮不上忙。启用票据后,SPA 就可以调用 GET /bff/ws-ticket,把这个短时效的一次性票据放到连接 URL 上,再由您的 API 去兑换。请在每次连接之前才签发一个:它在首次使用后即被删除,并在数秒内过期。

选项默认值作用
WsTicketsEnabledfalse启用 ws-ticket 端点。默认关闭。
WsTicketLifetime30s票据的有效时长。由于它要在 URL 中传输,这里刻意保持很短。
TicketExchangeParams[]票据请求可以转发进令牌交换的查询参数,使票据绑定到该上下文,而不是对一切都有效。

有意把令牌交给浏览器

BFF 的全部意义就在于浏览器不持有任何令牌,因此这项能力需要主动开启,适用面也很窄。它只为 Cookie 模型够不到的那一种情况而存在:位于另一个来源的资源服务器,例如您嵌入在 iframe 中的应用,必须用 Bearer 令牌来调用。启用后,GET /bff/token?resource=… 会返回一个交换得到的令牌:会话的令牌被降权到某一个白名单资源,并绑定到任何白名单上下文参数。浏览器永远看不到会话自身的令牌,而它拿到的那个令牌时效很短,且只面向单一受众。指定了白名单之外资源的请求会被拒绝,正是这一点让它不会变成一台通用的令牌铸造机。

选项默认值作用
TokenEndpointEnabledfalse启用 token 端点。默认关闭。
TokenEndpointResources[]令牌可以指向的 resource 取值。其他一律拒绝。
TokenEndpointExchangeParams[]作为上下文绑定转发进交换的查询参数,例如 project_id。

用一个 BFF 服务多个租户

设置一个租户查询参数,一套部署就能服务众多租户:/bff/login?slug=acme 选定租户,解析器提供该租户的 authority 和客户端凭据,这个键随关联 Cookie 一路进入会话,后通道注销则从令牌的签发者解析出租户。默认解析器让单租户行为保持逐字节一致,因此只要您不使用这项功能,就不必为它付出任何代价。

运行多个实例

会话存放在 IBffSessionStore 之后,其默认实现是进程内缓存。这对单个实例没问题,对多个实例则是错的:用户的下一个请求落到另一个副本上时就会被登出。请在添加 BFF 之前注册一个共享存储,例如通过 IDistributedCache 使用 Redis。

仅有共享存储还不够

您还需要一把跨副本的刷新锁。刷新的单飞机制只在进程内生效,而会话及其轮换中的刷新令牌位于所有副本共享的存储里。两个副本可能读到同一个会话,各自判定需要刷新,然后用同一个刷新令牌去兑换。这与令牌被窃后的重放无法区分,而对重放的正确处理是撤销整个授权族,因此没有锁的多实例 BFF 会把登出用户变成家常便饭。 两种方式都可以提供这把锁:通过集群功能注册一个 ILeaseProvider,或者在您的会话存储上实现 IBffRefreshLockStore,后者就是一次带生存时间的条件写入,如果您本来就在跑 Redis,这是更近的一条路。当存储看起来是共享的却没有锁时,BFF 会在启动时就发出警告,而不是让您从一张支持工单里才发现。

值得了解的选项

完整清单,采用 .NET 的写法。Node 包以驼峰形式接受核心选项,因此 BasePath 对应 basePath,SessionLifetime 对应 sessionLifetimeSeconds。PersistentCookie、CorrelationLifetime(在 Node 中固定为 15 分钟)和 LoginPassthroughParams 仅适用于 .NET。

选项默认值作用
Authority-您的租户认证主机。OIDC 元数据从这里发现。除非您是多租户,由解析器提供它,否则此项必填。
ClientId-为该 BFF 注册的机密客户端 id。
ClientSecret-客户端密钥。BFF 是机密客户端,因此此项必填。
Scopeopenid profile offline_access请求的作用域。必须包含 offline_access,否则不会有刷新令牌,会话会随访问令牌一起结束。
BasePath/bffBFF 路由挂载的位置。
CallbackPath/bff/callbackOIDC 重定向 URI 的路径。必须与客户端注册时使用的一致。
CookieName__Host-agbff会话 Cookie 的名称。__Host- 前缀要求使用 HTTPS,因此在明文 HTTP 上做本地开发时需要换一个名称。
SessionLifetime8h会话最长可以存活多久。请把它设为与刷新令牌的绝对有效期一致,否则闲置的用户会在手里的凭据仍然有效时被登出。
PersistentCookiefalse关闭浏览器后 Cookie 是否仍然保留。无论哪种设置,刷新令牌都留在服务端。
CorrelationLifetime30m从发起登录到回调之间,一次登录最长可以花多久。它限定了承载 state、nonce 和 PKCE verifier 的那个 Cookie 的生存期,因此用户把登录页开着离开、过一会儿再回来,正是它必须扛得住的情况。
RefreshThresholdSeconds60在访问令牌过期前多少秒对其进行刷新。
AntiForgeryHeaderX-Authagonal-Bff浏览器在非导航请求上必须发送的标头名称。
PostLogoutRedirectUri-注销完成后浏览器最终停留的位置。
ReturnUrlAllowlist[]非相对的 returnUrl 可以指向的绝对来源。相对路径始终允许,其余一律强制归为 /,因此无法通过登录路由构造出开放重定向。
LoginPassthroughParams[]从 /bff/login 转发到 authorize 请求的查询参数,例如 prompt,好让「立即开始」这类链接直接进入注册而不是登录。
TenantQueryParam-设置它即可用一个 BFF 服务多个租户。参见上文。

两种运行时行为一致

.NET 包和 Node 包实现的是同一份核心协议契约,因此登录、回调、用户、注销和后通道注销、Cookie、防伪标头、刷新行为以及基本的上游代理都完全相同。WebSocket 票据、令牌端点、交换路由、StripPrefix、颁发机构门控和匿名代理仅适用于 .NET。上面给出的是 .NET 的写法,Node 使用对应的驼峰形式。

每个部件都可替换

这些部件都是接口,因此您无需 fork 就能把 BFF 搬到自己的基础设施上:IBffSessionStore 决定会话存放在哪里,ICookieProtector 负责 Cookie 加密(默认使用 ASP.NET Data Protection),ITokenClient 负责与令牌端点和撤销端点通信。通过 IBffTenantResolver,一个 BFF 还可以服务多个租户:登录时从查询参数选择租户,后通道注销时则从令牌的签发者解析出租户。

用 AI 助手来操作您的门户

把 AI 助手连到您的租户,然后让它去做那些您原本要一路点击才能完成的事:找出一个登录不了的用户,查看他们是否还有第二验证因素,邀请某个人,看看谁持有管理员角色。

这是一次普通的 OAuth 连接,而不是一把 API 密钥。每个人都以自己的身份登录并批准该访问,因此助手能做的恰好是那个人在门户中能做的事,不会更多。不会新建任何可能泄露的东西,撤销某一个助手也不会影响其他任何人。

启用它

打开设置并启用 AI 助手访问。在您启用之前它是关闭的,而在关闭期间,该端点并不存在,而不只是拒绝请求。启用之后,面板会显示要粘贴到您的 AI 客户端里的 URL:

您的 MCP URL
https://portal-api.authagonal.io/api/v1/mcp/{your-tenant}

助手是怎么拿到权限的

助手必须先自行注册,才能发起一次登录,而允许它这么做的正是启用 AI 助手访问这一项。您不需要再打开别的开关,尤其不要为此在面向最终用户的租户上启用动态客户端注册:那项设置管的是为您自己的用户提供服务的租户,而助手并不在那里登录。注册并不等于拿到权限。刚注册好的助手什么都拿不到,直到您团队里的某个人登录并批准它,此后每个工具都会重新核对那个人的角色。

助手能做什么

恰好是连接它的那个人能做的事,按每次调用依据那个人自己的令牌来判定。tenant:support 坐席拿到的是诊断类和日常类工具;管理员工具不只是对他们隐藏起来,而是即便直接点名调用也会被拒绝。门户里受限的事情在这里同样受限:修改用户的电子邮箱地址在那里需要管理员角色,在这里也需要。

因为这是一份普通的授权,您就按普通方式管理它:在您账户的已授权应用页面上移除该助手,它就无法再刷新,因此会在当前访问令牌过期时停止工作。助手做出的每一项更改都会以它所代表的那个人的名义写进您的审计日志,因此这条线索还是您一直在看的那一条。

这些工具

本次发布共三十三个。由您的 AI 客户端决定启用哪些,因此如果您只想让助手做读取,就可以只把读取类工具交给它。

工具角色作用
find_user支持按电子邮箱、姓名前缀或 id 查找用户。其他一切的起点。
get_user支持一个用户的完整信息,包括是否处于活动状态、是否已确认以及是否被锁定。
get_user_mfa支持某个人已注册了哪些第二验证因素。
get_user_sessions支持用户当前在哪些地方处于登录状态。
search_audit支持按操作者、动作或被操作的对象检索审计日志。
list_users支持列出目录,可选择只筛选某一个组织。
get_user_stats支持您有多少用户,以及其中有多少人使用第二验证因素。
invite_user支持通过电子邮件邀请某个人。
resend_invite支持再发一次邀请。
send_verification_email支持再发一次电子邮箱验证邮件。
update_user支持更新一份资料。更改邮箱地址仍然需要管理员权限。
revoke_user_sessions支持让一个用户在所有地方退出登录。
list_roles管理员您租户中定义的角色。
list_role_members管理员谁持有指定的角色。
assign_role管理员给一个用户授予某个角色。
unassign_role管理员收回一个角色。
reset_user_mfa管理员移除全部第二验证因素,供丢失了验证器的人使用。
get_settings管理员您租户的配置。
list_sso_connections管理员您的 SSO 连接以及它们覆盖的域。
list_organizations管理员您租户中的组织,每次一页。
get_organization管理员按 id 或 slug 获取一个组织,含其电子邮件域。
create_organization管理员创建组织。slug 是永久的。
update_organization管理员更改组织的名称、策略选项、元数据或品牌。slug 不能更改。
delete_organization管理员删除组织及其所有成员关系。
list_organization_members管理员组织的成员,每次一页。
add_organization_member管理员将用户添加到组织。
update_organization_member管理员更改成员在组织内的状态或角色。
remove_organization_member管理员将用户从组织中移除。
add_organization_domain管理员为组织声明一个电子邮件域。返回需要发布的 DNS TXT 记录。
verify_organization_domain管理员检查 TXT 记录并将该域标记为已验证。可安全重复执行。
remove_organization_domain管理员放弃对某个域的声明。现有成员保留。
list_user_organizations管理员用户所属的组织,以及其在每个组织中的状态和角色。
list_clients开发者您租户中已注册的 OAuth 客户端。

读取与写入都有标注

每个工具都会告诉您的客户端:它是否只读,是否会改动什么,以及那次改动是否具有破坏性。好的客户端会据此在不打扰您的情况下执行一次查询,并在诸如移除第二验证因素这类操作之前先停下来。请把它当作一种便利,而不是一种管控:真正决定助手可以做什么的,是您自己的角色,每次调用都会检查。

目前还没有包含的内容

删除用户、创建或编辑 SSO 连接、计费、备份和客户端密钥在本次发布中都没有。删除属于您的数据擦除队列,而不是摆在它旁边;SSO 连接的影响面大到一次改错就会把整支员工队伍挡在门外,所以它们目前是只读的;而一个会返回客户端密钥的工具,等于把密钥留在助手的对话记录里。请告诉我们您想要其中哪些,以及按什么顺序。

MCP 服务器身份验证

如果您对外提供 Model Context Protocol 服务器,Authagonal 可以充当它背后的授权服务器。AI 助手发起连接,它背后的人登录并授予访问权限,您的服务器则收到一个普通的 bearer 令牌,像验证任何其他 API 一样验证它。

另一种做法是把 API 密钥粘贴进助手的配置里,那是一份背后没有用户的凭据,没有有效期,没有同意步骤,也没有办法在不为所有人轮换密钥的前提下撤销某一个连接器。改用 OAuth 意味着这份授权属于一个具名的人,会出现在您的审计日志里,并且可以在门户中撤销,而不必动其他任何东西。

连接是怎么建立的

整个交换过程都由发现机制驱动,因此符合规范的客户端除了您服务器的 URL 之外不需要配置任何东西。

步骤发生了什么
1连接器不带令牌调用您的 MCP 服务器,得到一个 401,其中写明去哪里查找。
2它获取您的受保护资源元数据,其中指明您的 Authagonal 租户就是授权服务器。
3它获取该租户的授权服务器元数据,并且由于从未在任何地方注册过,就自行注册。
4它把用户送去登录并批准该访问,同时指明您的 MCP 服务器就是它想要令牌的那个资源。
5它带着拿到的 bearer 令牌再次调用您的服务器,该令牌只对您的服务器和那位用户有效。

这个顺序里没有一步是 Authagonal 特有的:它就是 MCP 授权规范,建立在 RFC 9728(资源元数据)、RFC 8414(发现授权服务器)、RFC 7591(注册)和 RFC 8707(指明资源)之上。遵循该规范的连接器无需为我们做特殊处理就能工作。

让连接器自行注册

您从未打过交道的连接器无法使用您手工创建的客户端,因此它会在运行时注册一个。该功能默认关闭。在设置中打开动态客户端注册,注册端点就会出现在您的发现文档里;保持关闭,该端点就不会被公布,并且会拒绝请求。启用它只为您自己的租户打开注册,绝不会为别人的租户打开。

防护发生了什么
授权类型只能注册 authorization code 流程和 refresh 流程。自行注册无法铸造出一个完全绕过用户的机器对机器客户端。
PKCE对每个已注册的客户端强制启用,无论注册请求要的是什么。
授权同意同样强制。已注册的连接器在有人看过它所请求的内容并批准之前,拿不到令牌。
作用域OIDC 内置的作用域始终可用。除此之外,自行注册的客户端只能请求一个名为 mcp 的作用域,并且前提是您的租户已定义该作用域且未设角色限制。roles 和 groups 无法通过自行注册获得。
速率限制每个 IP 地址每小时十次注册,因此开放的端点无法被用来塞满您的客户端库。

两条发现路径都提供服务

MCP 客户端通过 /.well-known/oauth-authorization-server(RFC 8414)解析授权服务器,而 OIDC 客户端使用 /.well-known/openid-configuration。您的租户在两条路径上都返回相同的元数据,因此遵循 MCP 规范的连接器无需别人告知去哪里查找,就能找到您。

您的 MCP 服务器要实现什么

两件小事,之后它就是一台普通的资源服务器。第一,发布受保护资源元数据,指明您的租户是授权服务器。把它放在 well-known 路径上提供;如果您的 MCP 端点位于子路径上,也要在带路径后缀的形式上提供,因为客户端两种都会试。

受保护资源元数据
GET https://your-app.example/.well-known/oauth-protected-resource

{
  "resource": "https://your-app.example/mcp",
  "authorization_servers": ["https://acme.authagonal.io"],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["mcp"]
}

第二,当一次调用没有带有效令牌时,返回 401 并附上指向那份元数据的 WWW-Authenticate 头。正是这个头把一次拒绝变成一次连接:没有它,客户端就无从发现该去哪里认证,只会直接失败。

验证令牌
// Validate the connector's token like any other resource server.
builder.Services.AddAuthentication().AddJwtBearer("McpBearer", o =>
{
    o.Authority = "https://acme.authagonal.io";              // your tenant
    o.TokenValidationParameters.ValidAudience = "https://your-app.example/mcp";
});

// Unauthenticated? Point the connector at the metadata rather than just refusing.
http.Response.Headers.WWWAuthenticate =
    "Bearer resource_metadata=\"https://your-app.example/.well-known/oauth-protected-resource\"";
return Results.Unauthorized();

检查受众,而不只是签名

要验证令牌是为您的服务器签发的。连接器把您的 MCP 服务器指明为资源,所以令牌的受众就是您的资源 URL。只检查签名和签发者的资源服务器,会接受同一租户内为另一个资源签发的令牌,一个连接器的访问权限就是这样变成了另一个连接器的。

作用域、方案与撤销

在您的作用域页面上定义一个名为 mcp 的作用域,不设角色限制,自行注册的连接器就可以请求它;它会出现在授权同意屏幕上,让用户看到自己在批准什么。令牌的主体是登录的那个人,因此您的服务器可以决定这个具体的人能做什么,而不是把每个连接器都一视同仁。自行注册的连接器无法请求 roles 或 groups 作用域,因此请在您这一侧查询这些信息,而不要指望令牌中带有它们。

因为这是一份普通的 OAuth 授权,撤销也就按普通方式进行:用户在其账户的已授权应用页面上移除该连接器,这会删除它的授权和刷新令牌,使其无法续期。它现有的访问令牌会立即被 /connect/introspect 和 /connect/userinfo 拒绝;仅在本地验证 JWT 的服务器会继续接受这样的令牌,直到其过期。审计日志会记录每次登录及其对应的客户端。

设置发生了什么
Dynamic client registration允许连接器自行注册的门户设置。默认关闭。
mcp除 OIDC 内置作用域之外,自行注册的客户端唯一可以请求的作用域。在您的租户中定义它且不设角色限制,即可提供它。
resource连接器发送的、用来指明您的 MCP 服务器的参数,它把令牌的受众收窄到该服务器。

构建自定义登录界面

用您自己的界面替换 Authagonal 托管的登录、注册、密码重置和 MFA 界面,同时让 Authagonal 继续处理身份验证、MFA、SSO、会话和令牌签发。有两种方式:使用我们的 React 组件库,或从任意框架直接调用认证 API。此功能需主动启用:请先在设置 → 会话下启用自定义登录界面。

A fully customized hosted login page with a tenant logo, brand color, and support email in the footer

前提条件:在您的根域名上启用自定义域名

登录会话是第一方 Cookie,因此您的界面与 Authagonal 认证服务器必须共享同一个可注册域名。请在与您应用运行所用的相同根域名下,将一个自定义认证域名指向 Authagonal——例如认证位于 login.acme.com,应用位于 app.acme.com。在存在已启用的自定义域名之前,自定义登录界面设置将保持禁用状态。

您的界面认证主机是否可用?
app.acme.comlogin.acme.com✅ 相同根域名
acme.comauth.acme.com✅ 相同根域名
app.acme.comacme.authagonal.io❌ 跨站点
myapp.iologin.acme.com❌ 跨站点

为什么必须使用自定义域名

跨站点的会话 Cookie 会成为第三方 Cookie——而浏览器(Safari、Chrome)正在逐步淘汰它。将认证保留在您自己的根域名下可使 Cookie 成为第一方且面向未来,这也是平台所强制的:跨域认证调用只有来自与认证主机共享根域名的来源时才会被接受。

/api/auth 调用无需任何客户端 CORS 设置:一旦开启自定义登录界面,与您的认证域名属于同一可注册域的任何来源都会被自动允许。仅当浏览器也执行令牌交换时,才需要将您界面的来源(例如 https://app.acme.com)添加到客户端的允许的 CORS 来源(客户端 → URIs)中。

React:@authagonal/login

npm i @authagonal/login 将认证逻辑和界面作为同一个包提供——与 Authagonal 托管登录所基于的包相同。选择适合您的层级:

  • 完整应用——直接引入 App 并通过品牌设置为其设定主题。
  • 组合页面——在您自己的布局中使用 LoginPage、MfaChallengePage、ResetPasswordPage…
  • 基础组件 + 逻辑——使用 AuthLayout/Button/Input 和 API 客户端(login、mfaVerify、forgotPassword…)构建您自己的界面。
使用 @authagonal/login API 的自定义界面
import { AuthLayout, Input, Button, login, ApiRequestError } from '@authagonal/login';

// returnUrl: the /connect/authorize URL your login page was opened with
function MyLogin({ returnUrl }: { returnUrl: string }) {
  async function onSubmit(email: string, password: string) {
    try {
      const res = await login(email, password, returnUrl);  // POST /api/auth/login (sets the session cookie)
      if (res.mfaRequired) {/* render your MFA step, then mfaVerify(res.challengeId!, 'totp', code) */}
      else if (res.mfaSetupRequired) {/* enrol first: mfaTotpSetup(res.setupToken) */}
      else window.location.href = returnUrl;                 // resume /connect/authorize
    } catch (e) {
      if (e instanceof ApiRequestError) {/* show e.message; e.error is the error code */}
    }
  }
  return <AuthLayout>{/* your own markup + <Input/> <Button/> */}</AuthLayout>;
}

任意框架:调用认证 API

不使用 React?可直接调用认证流程端点(位于 /api/auth 下),然后移交给标准的 OIDC /connect/authorize 流程。请发送 credentials: 'include' 以便存储会话 Cookie。

端点用途
POST /api/auth/login进行身份验证;返回 mfaRequired 或返回 URL
POST /api/auth/register自助注册(启用时)
POST /api/auth/forgot-password发起密码重置
POST /api/auth/reset-password完成密码重置
GET /api/auth/password-policy密码策略(用于渲染规则)
POST /api/auth/mfa/*MFA 设置 + 验证(TOTP、WebAuthn、恢复码)

使用 credentials: 'include'

会话是一个 Cookie,因此您的请求必须发送凭据。跨域调用只有在启用了自定义登录界面,且您的来源与认证主机共享根域名时才会成功——否则将被以 403 拒绝。
先进行身份验证,然后移交给 OIDC
# 1. Authenticate (browser fetch; credentials:'include' so the session cookie is stored)
curl -i -X POST https://login.acme.com/api/auth/login \
  -H "Content-Type: application/json" \
  -H "Origin: https://app.acme.com" \
  --data '{"email":"[email protected]","password":"..."}'
# (handle {"mfaRequired":true} → POST /api/auth/mfa/verify, then continue)

# 2. Hand off to the OAuth flow: top-level navigation to the authorize endpoint with PKCE:
# https://login.acme.com/connect/authorize?client_id=my-app&redirect_uri=...&response_type=code
#   &scope=openid%20profile%20email&code_challenge=...&code_challenge_method=S256
# The session cookie (same-site) authenticates the user; you get back a code → exchange at /connect/token.

一个租户,多个客户

适用于一个集成商、一个租户和 N 个品牌客户的模式,每个客户都有自己的登录域名和自己的用户,而无需为每个客户创建一个租户。声明式配置和组织的存在,正是为了把这件事变成一次配置变更,而不是一项工程任务。

整体结构

  • 在单个租户内,每个客户一个组织。
  • 每个客户一个品牌登录域名,各自固定到该客户的组织:在该域名上登录只能为该组织签发令牌。
  • 应用同样按客户主机提供服务,全部来自同一次部署:代码、构建或镜像在客户之间没有任何差异。
  • 依赖方根据其运行所在的主机来选择授权服务器,而不是使用构建时的常量:同一个包会根据由哪个客户主机提供服务而连接到不同的登录域名。

分步指南

配置客户。使用指定新组织及其域名的规范调用声明式配置端点,该调用是幂等的,并按部分划分范围。

配置规范
{
  "organizations": [
    { "slug": "acme", "name": "Acme Pty Ltd" }
  ],
  "customDomains": [
    { "domain": "login.acme.example", "organizationSlug": "acme" }
  ]
}

将客户的 DNS 指向租户。从客户自己的区域添加一条 CNAME,证明控制权和意图,无需 TXT 令牌。

CNAME record
_authagonal-challenge.login.acme.example.  CNAME  <slug>.<platformDomain>.

与平台使用同一个 Cloudflare 账户?

该记录必须为仅 DNS(不代理):在同一账户内的两个区域之间使用代理的 CNAME 永远到不了平台的 SaaS 自定义主机名。位于不同账户的客户区域则根本没有可能设错的代理开关。

将应用指向客户的主机。依赖方按请求、根据当前所服务的主机解析自己的 OIDC 授权服务器:客户映射中没有条目的主机会回退到经典的单租户推导方式,因此添加客户纯属增量操作。

oidc.ts
export function resolveCustomer(): { authority: string; clientId: string } {
  const cfg = getConfig();
  const customer = cfg.customers?.[window.location.host];
  if (customer) return customer;
  const baseDomain = cfg.baseDomain || window.location.hostname.replace(/^consumer\./, '');
  return { authority: `https://${cfg.tenantSlug}.${baseDomain}`, clientId: cfg.clientId };
}

客户端不需要任何与组织相关的内容:没有 organization 参数,也没有每客户的 scope。域名固定完全在服务器端完成组织选择,这才使得“每个客户的应用都是同一份代码”名副其实。

客户看到的内容

登录页面会渲染每个组织自己的品牌,并合并到租户品牌之上,参见品牌覆盖。每个客户都可以拥有自己的应用名称、徽标、颜色和支持邮箱,而不影响其他客户。

客户 A 的用户如果尝试在客户 B 的主机上登录,会被拒绝,而不是看到客户 B 的数据。根据冲突产生的方式,有两种形态:

  • 依赖方什么都不发送,只是落在固定域名上:固定值代表用户提供了组织,选择随之变为明确,而在 B 中没有成员关系的客户 A 用户会在即将签发令牌时被拒绝。
  • 依赖方自己指定了不同的组织:由域名固定中间件直接以 400 拒绝,在显示任何登录页面之前。

成员关系拒绝会重定向回应用

上述第一种形态并不是损坏的登录页面,而是一个普通的 error=access_denied OIDC 重定向,回到依赖方自己的 redirect_uri,并附带一段说明具体缺少哪个组织成员关系的描述。

接入第 N+1 个客户

由 Terraform 驱动配置调用时,接入另一个客户只需编辑一个文件:在 organizations 数组中再加一项,再加一个固定它的域名条目,再加一条 DNS 记录。应用、其部署和构建都不需要任何改动,其配置条目一存在,基于主机的解析器就会立即接纳新客户。

参考实现

一个持久的开发租户针对两个真实的客户域名端到端地演练整个模式,并配有端到端测试套件,用于配置成员、检查品牌和 org_id 声明,并断言上述两种拒绝形态:这是可执行的文档,而不只是一段描述。

在本地运行
cd e2e-consumer && DOMAIN=authagonal.dev npx playwright test organizations-domains

该套件与其他所有测试规范一样默认运行;在未配置参考域名的环境中可以跳过它,因为它是套件中唯一依赖仓库自身控制范围之外基础设施的规范。

方案与限额

Authagonal 提供一个免费版和四个付费方案级别。每个方案都包含所有身份验证功能。各方案的区别在于月活跃用户(MAU)限额和超额定价,并且免费版仅提供社区支持,不含租户支持台。

方案级别

方案MAU 限额超额超额费用/用户
免费版250否—
入门版1,000否—
专业版5,000是$0.04/用户
规模版25,000是$0.025/用户
企业版100,000是$0.015/用户

月活跃用户(MAU)

月活跃用户是在一个日历月(UTC)内至少成功完成一次身份验证的唯一用户。通过 SCIM 配置但尚未登录的用户不计入您的 MAU 总数。

超额:如果您的方案支持超额(专业版及以上),并且您的租户已启用超额(默认关闭),超出 MAU 限额的用户将按上方方案表中显示的每用户费率计费。超额上限设定了在限额之外允许的最大额外用户数。

强制执行:如果您的方案不支持超额(免费版、入门版)或未启用超额,本月已经登录过的用户始终保留访问权限。当租户首次超出限额时,会开始一个 10 天的宽限期,在此期间新用户仍可登录。宽限期结束后,本月尚未登录过的用户将被拒绝,直到下个月或您升级方案为止。

每个方案均包含完整功能

所有方案包含完整功能集——SSO、SCIM、MFA、自定义域名、品牌、Webhook、审计日志和门户。