身份验证

本页内容

EmDash 将通行密钥(passkey)认证作为主要登录方式。通行密钥防钓鱼、无需密码,并可通过浏览器或密码管理器跨设备使用。

除通行密钥外,您还可以添加可插拔的登录提供方。GitHub、Google 与 Microsoft 随 EmDash 附带。单独安装的 Atmosphere 提供方 可加入 AT Protocol 账户,同一提供方接口也对其他包开放。文档中的 GitHub、Google、Microsoft 与 Atmosphere 提供方可以创建首个管理员账户,或让已关联的 EmDash 用户登录。

对于 Cloudflare 部署,Cloudflare Access 是生产环境中独立且独占的认证模式。它在受保护的 EmDash 路由上验证 Access 凭据,而不是展示 EmDash 的登录方式。

选择认证模式

通行密钥使用 WebAuthn——一种在设备上存储或通过密码管理器同步公钥凭据的 Web 标准。登录时,设备证明持有该凭据,而不会在网络上发送密码。

通行密钥是默认方式。GitHub、Google、Microsoft 与 Atmosphere 提供方是额外的登录方法:各自认证用户、关联或创建 EmDash 账户,并建立与通行密钥登录相同的 EmDash 会话。

通行密钥认证提供:

  • 无需记忆或泄露密码
  • 防钓鱼 — 凭据绑定到站点域名
  • 跨设备同步 — 适用于 iCloud Keychain、Google Password Manager、1Password 等
  • 快速登录 — 生物识别或 PIN 一键完成

Cloudflare Access 使用 auth 选项而非 authProviders。在生产环境中,它成为受保护 /_emdash 路由的权威。EmDash 仍会存储本地用户,以便角色、所有权与禁用用户检查继续生效。

设置首个用户

首次访问管理面板时,设置向导会引导您创建管理员账户。

  1. 前往 http://localhost:4321/_emdash/admin

  2. 在 Set up your site 中,输入站点标题与可选标语。模板也可以提供示例内容。选择 Continue。

  3. 在 Create your account 中,输入电子邮件地址与可选姓名。选择 Continue。

  4. 在 Secure your account 中,创建通行密钥或选择已配置的登录提供方之一。若选择通行密钥,浏览器会询问保存位置:

    • macOS:Touch ID、设备密码或安全密钥
    • Windows:Windows Hello 或安全密钥
    • 移动端:Face ID、指纹或 PIN
  5. 完成浏览器或提供方流程。EmDash 将首个用户创建为 Admin 并打开仪表盘。

使用通行密钥登录

设置完成后,再次进入管理面板会触发通行密钥认证:

  1. 访问 /_emdash/admin

  2. 若未登录,将看到登录页

  3. 点击 Sign in 进行认证

  4. 浏览器会提示通行密钥(生物识别、PIN 或安全密钥)

  5. 验证后,您将被重定向到管理仪表盘

使用魔法链接登录

若无法使用通行密钥,魔法链接可提供替代方案。站点必须先配置邮件提供方,EmDash 才能发送链接——参见邮件设置。

  1. 在登录页点击 Sign in with email

  2. 输入电子邮件地址

  3. 检查收件箱中的登录链接

  4. 点击链接(15 分钟内有效),然后在确认页选择 Continue

    仅在选择 Continue 时才会使用该链接,因此提前打开链接的邮件安全扫描器不会把它用掉。

配置登录提供方

除通行密钥外,EmDash 支持可插拔的登录提供方,它们会出现在登录页与设置向导中。GitHub、Google 与 Microsoft 随 EmDash 附带。Atmosphere 与第三方提供方是通过同一接口注册的独立包。

提供方是叠加的——启用提供方后通行密钥仍然可用。仅当提供方给出相同的已验证电子邮件地址时,GitHub、Google 与 Microsoft 才会自动关联现有 EmDash 用户。Atmosphere 账户按去中心化标识符(DID)关联,因为 EmDash 的 Atmosphere 流程不会收到电子邮件地址。每个内置提供方都可以创建首个用户,因此全新安装可以完全跳过通行密钥。

向 Astro 添加提供方

将提供方传入 EmDash 集成的 authProviders 数组。以下示例启用 GitHub、Google 与 Atmosphere:

import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
import { github } from "emdash/auth/providers/github";
import { google } from "emdash/auth/providers/google";
import { atproto } from "@emdash-cms/auth-atproto";

export default defineConfig({
	integrations: [
		emdash({
			authProviders: [github(), google(), atproto()],
		}),
	],
});

登录页的顺序很重要:提供方按您列出的顺序渲染,仅按钮的紧凑提供方在前,需要自定义表单的提供方(例如需要输入 handle 的 Atmosphere)在后。

GitHub

以下示例启用 GitHub 提供方:

import { github } from "emdash/auth/providers/github";

emdash({ authProviders: [github()] });

通过环境变量设置凭据。EmDash 先检查带前缀的名称,再回退到无前缀名称:

变量用途
EMDASH_OAUTH_GITHUB_CLIENT_ID / GITHUB_CLIENT_IDOAuth 应用客户端 ID
EMDASH_OAUTH_GITHUB_CLIENT_SECRET / GITHUB_CLIENT_SECRETOAuth 应用密钥

将 GitHub OAuth 应用的回调 URL 配置为 https://your-site.example.com/_emdash/api/auth/oauth/github/callback。

Google

以下示例启用 Google 提供方:

import { google } from "emdash/auth/providers/google";

emdash({ authProviders: [google()] });

通过环境变量设置凭据。EmDash 先检查带前缀的名称,再回退到无前缀名称:

变量用途
EMDASH_OAUTH_GOOGLE_CLIENT_ID / GOOGLE_CLIENT_IDOAuth 应用客户端 ID
EMDASH_OAUTH_GOOGLE_CLIENT_SECRET / GOOGLE_CLIENT_SECRETOAuth 应用密钥

将 Google OAuth 客户端的重定向 URI 配置为 https://your-site.example.com/_emdash/api/auth/oauth/google/callback。

Microsoft

以下示例启用 Microsoft 提供方:

import { microsoft } from "emdash/auth/providers/microsoft";

emdash({ authProviders: [microsoft()] });

在 Microsoft Entra 管理中心注册一个应用,添加 Web 重定向 URI https://your-site.example.com/_emdash/api/auth/oauth/microsoft/callback,并创建客户端密钥。然后设置全部三个环境变量。EmDash 先检查带前缀的名称,再回退到无前缀名称:

变量用途
EMDASH_OAUTH_MICROSOFT_CLIENT_ID / MICROSOFT_CLIENT_ID应用程序(客户端)ID
EMDASH_OAUTH_MICROSOFT_CLIENT_SECRET / MICROSOFT_CLIENT_SECRET客户端密钥的值
EMDASH_OAUTH_MICROSOFT_TENANT_ID / MICROSOFT_TENANT_ID目录(租户)ID,或 common、organizations、consumers

在 Microsoft Entra 管理中心创建的客户端密钥最长 24 个月后过期,密钥过期后 Microsoft 登录将停止工作。请在此之前创建新密钥并更新 EMDASH_OAUTH_MICROSOFT_CLIENT_SECRET。

请使用应用概述页面上的目录 ID。诸如 contoso.onmicrosoft.com 之类的域名不能作为租户。若要使用 common、organizations 或 consumers,请为应用注册相应受支持的账户类型,因为 Microsoft 会拒绝注册信息不允许的登录。

EmDash 从账户的电子邮件中获取地址;若账户没有电子邮件地址,则从其登录名中获取。只有对于它视为已验证的地址,EmDash 才会关联现有用户、接受邀请,并允许自助注册(包括首个管理员账户)。

使用目录 ID 时,通过该目录登录的账户,其地址若与账户登录名(用户主体名称,UPN)位于同一域名,则视为已验证,因为 Microsoft 只允许登录名使用目录已验证的域名。通过其他目录或身份提供方登录的账户(例如来宾,或创建该目录的个人 Microsoft 账户)的地址不视为已验证;当租户为 common、organizations 或 consumers 时,任何地址都不视为已验证。

若用户的电子邮件地址与其登录名位于不同域名,请在应用注册中添加 xms_edov 作为可选的 ID 令牌声明。这样,当该声明确认目录已验证该地址的域名时,EmDash 就会将此类地址视为已验证。

以下示例将每个 Microsoft 地址都视为已验证:

emdash({ authProviders: [microsoft({ emailVerified: true })] });

仅当您信任所有账户可登录的每个目录时,才设置 emailVerified: true。Microsoft 不会验证其报告的电子邮件地址,且账户所属目录的管理员可以更改该地址。设置 emailVerified: false 会将每个地址都视为未验证,因此只有已关联到 EmDash 用户的账户才能登录。

管理 Microsoft 访问

若只允许目录中的部分账户登录,请在 Microsoft Entra 中应用的企业应用程序属性里,将 Assignment required 设为 Yes,然后分配需要访问权限的用户或组。Microsoft 会在其他所有人到达 EmDash 之前就拒绝他们。分配组需要 Microsoft Entra ID P1 或 P2,且仅涵盖直接成员。

Microsoft Entra 决定谁可以使用 Microsoft 登录。EmDash 保留自己的用户、角色与禁用用户标志,目录中的更改不会同步到 EmDash:

  • 若某个 EmDash 用户已使用该地址,则首次使用已验证地址进行的 Microsoft 登录会关联到该用户并保留其角色。使用未验证地址的登录会被拒绝。
  • 若用户已被邀请,则通过邀请链接使用 Microsoft 登录会赋予其受邀角色。不通过链接的登录则适用允许的域名规则。
  • 有人离开时,除其 Microsoft 账户外,也请禁用其 EmDash 用户。被禁用或删除的 Microsoft 账户会阻止新的 Microsoft 登录,但不会终止已打开的 EmDash 会话,也不会影响在 EmDash 中注册的通行密钥。
  • 若新的 Microsoft 账户之后获得了相同的地址,它会关联到现有 EmDash 用户并获得该用户的角色。被禁用的 EmDash 用户会保持禁用状态,直到管理员将其启用。
  • 若已删除的 Microsoft 账户在 Microsoft Entra 中被恢复,它会登录到与之前相同的 EmDash 用户。

Atmosphere(AT Protocol)

若站点的协作者已有 Atmosphere 账户——Bluesky 与更广泛 AT Protocol 网络背后的用户自有身份——请安装 Atmosphere 提供方:

pnpm add @emdash-cms/auth-atproto

以下示例使用 handle 允许列表启用 Atmosphere 提供方:

import { atproto } from "@emdash-cms/auth-atproto";

emdash({
	authProviders: [
		atproto({
			allowedHandles: ["*.example.com"],
		}),
	],
});

无需客户端密钥或环境变量。关于 handle/DID 允许列表、角色映射以及 AT Protocol OAuth 配置文件要求的本地开发设置,请参阅 Atmosphere 登录指南。

构建提供方

提供方是一个 AuthProviderDescriptor:包含 id、人类可读标签,以及登录流程所需的管理组件、路由处理程序、公开路由前缀与存储集合。若提供方应出现在首个用户设置中,请从 adminEntry 导出 SetupStep。该形状从 emdash 导出:

import type { AuthProviderDescriptor } from "emdash";

export function myProvider(): AuthProviderDescriptor {
	return {
		id: "my-provider",
		label: "My Provider",
		adminEntry: "my-provider/admin", // exports LoginButton / LoginForm / SetupStep
		routes: [
			{ pattern: "/_emdash/api/auth/my-provider/login", entrypoint: "my-provider/routes/login.ts" },
			{ pattern: "/_emdash/api/auth/my-provider/callback", entrypoint: "my-provider/routes/callback.ts" },
		],
		publicRoutes: ["/_emdash/api/auth/my-provider/"],
		storage: {
			sessions: {},
		},
	};
}

Atmosphere 包(@emdash-cms/auth-atproto)是需要自定义登录表单、OAuth 路由处理程序与持久存储的提供方最完整的真实参考。

用户角色

EmDash 使用五级基于角色的访问控制:

角色级别说明
Subscriber10阅读已发布内容(无草稿访问)
Contributor20创建内容(发布需审批)
Author30创建/编辑/发布自己的内容
Editor40管理全部内容
Admin50完全访问,包括设置

每个角色继承所有较低级别的权限。首个用户始终创建为 Admin。

订阅者与草稿内容

订阅者持有 content:read 权限,以便仅限成员的已发布内容可提供给已认证读者。他们无法查看草稿、定时项、回收站项、修订或预览 URL——这些由授予 Contributor 及以上的 content:read_drafts 控制。list 与 get 端点对订阅者透明地过滤为 status=published;仅编辑器视图(/compare、/revisions、/trash、/preview-url)会直接拒绝订阅者请求。

邀请用户

管理员可通过管理面板邀请新用户:

  1. 在管理后台侧边栏中,选择 Users

  2. 点击 Invite User

  3. 输入用户的电子邮件并选择角色

  4. 点击 Send Invite

  5. 若已配置邮件,EmDash 会发送邀请。否则,复制生成的链接并自行发给用户。

  6. 他们打开链接,并用邀请页提供的通行密钥或登录提供方创建账户。

邀请链接为一次性使用,7 天后过期。

管理通行密钥

用户可在账户设置中管理通行密钥:

  • Add passkey — 为备份或其他设备注册额外通行密钥
  • Remove passkey — 删除不再使用的通行密钥
  • Rename passkey — 为通行密钥起描述性名称

每位用户最多可注册 10 个通行密钥。

EmDash 不允许用户删除最后一把通行密钥。删除旧密钥前请先添加替代密钥。

通行密钥只能在创建它的地址上使用。将站点迁移到新域名后,请参阅迁移后登录。

允许群体在无邀请的情况下登录

要在不逐一邀请的情况下允许群体登录,请配置带允许列表的登录提供方。Atmosphere 提供方接受 allowedHandles 与 allowedDIDs(参见 Atmosphere 登录);Cloudflare Access 适配器通过 autoProvision 与 roleMapping 从身份提供方预配用户。文档中的 GitHub、Google、Microsoft 与 Atmosphere 提供方也可以创建初始管理员账户。

会话

通行密钥、魔法链接、邀请与登录提供方的回调会将 EmDash 用户 ID 存入 Astro 的会话存储。浏览器收到 Astro 不透明的 astro-session 标识符;用户与凭据记录仍保留在 EmDash 数据库中。

Cloudflare Access 也会将解析后的 EmDash 用户写入 Astro 会话。这样公开页面在读取 Astro.locals.user 时可识别已登录用户。会话不会取代受保护 /_emdash 路由上的 Access 认证:EmDash 会在这些请求上再次验证 Access JSON Web Token(JWT)。

认证速率限制

EmDash 会限制启动登录、注册或 OAuth 客户端注册的未认证端点。限制对每个端点与受信任的客户端 IP 分别生效:

端点限制
POST /_emdash/api/auth/passkey/options每分钟 10 次请求
POST /_emdash/api/auth/magic-link/send每 5 分钟 3 次请求
POST /_emdash/api/auth/signup/request每 5 分钟 3 次请求
POST /_emdash/api/oauth/device/code每分钟 10 次请求
POST /_emdash/api/oauth/device/token每分钟 12 次请求
POST /_emdash/api/oauth/register每分钟 10 次注册

在 Cloudflare 上,EmDash 从 Cloudflare 请求元数据读取客户端 IP。位于反向代理后的自托管站点必须配置 trustedProxyHeaders,EmDash 才能使用代理的客户端 IP 头。当没有可用的受信任 IP 时,这些按 IP 检查会被跳过,因为没有安全的计数键。

通行密钥存储公钥凭据;私钥留在用户的认证器中。魔法链接令牌以 SHA-256 哈希存储,使用后删除。

故障排除

”No passkeys registered”

若登录时看到此错误,通行密钥可能已从密码管理器中删除。请让管理员发送恢复魔法链接;站点必须已配置邮件。

“Passkey authentication failed”

这通常表示通行密钥是为其他域名创建的。通行密钥与域名绑定——用于 localhost:4321 的通行密钥无法在 example.com 上使用。请为每个域名注册新的通行密钥。

丢失全部通行密钥

若您失去了对所有已注册通行密钥的访问:

  1. 请另一位管理员发送恢复魔法链接。站点必须已配置邮件。
  2. 在 15 分钟内打开链接并选择 Continue 登录。
  3. 在账户设置中注册新的通行密钥。

若您是唯一管理员且未配置邮件,则需要通过数据库重置站点的认证。

Cloudflare Access

部署到 Cloudflare 时,可以使用 Cloudflare Access 替代内置登录方式。Access 在边缘用您的身份提供方认证用户。EmDash 验证已签名的 Access JWT,加载该人的身份与组,并将该身份映射到本地 EmDash 用户。

何时使用 Cloudflare Access

  • 单点登录 — 用户通过公司 IdP 认证
  • 集中访问控制 — 在 Cloudflare 仪表盘管理谁可访问管理端
  • 无需管理通行密钥 — 不必注册或管理通行密钥
  • 基于组的角色 — 自动将 IdP 组映射到 EmDash 角色

设置 Access

  1. 为站点的 /_emdash/* 路径创建 Cloudflare Access 应用与策略。仅保护 /_emdash/admin/* 会使 REST API 缺少 EmDash 期望的 JWT。
  2. 复制应用的 Application Audience (AUD) Tag。
  3. 将标签存入运行时环境变量 CF_ACCESS_AUDIENCE。本地与已部署值请遵循 EmDash 密钥指南。
  4. 配置 EmDash 在运行时读取该值:
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
import emdash from "emdash/astro";
import { d1, access } from "@emdash-cms/cloudflare";

export default defineConfig({
	output: "server",
	adapter: cloudflare(),
	integrations: [
		emdash({
			database: d1({ binding: "DB" }),
			auth: access({
				teamDomain: "myteam.cloudflareaccess.com",
				audienceEnvVar: "CF_ACCESS_AUDIENCE",
			}),
		}),
	],
});

应用 audience 标识哪个 Access 应用签发了 JWT。EmDash 会连同签发者与签名一并验证;其他 Access 应用的令牌会被拒绝。

配置选项

选项类型默认值说明
teamDomainstring必填您的 Access 团队域名(例如 myteam.cloudflareaccess.com)
audiencestring—直接提供的 Application Audience (AUD) 标签。在 Workers 上优先使用 audienceEnvVar。
autoProvisionbooleantrue首次 Access 登录时创建 EmDash 用户
defaultRolenumber30未匹配任何组的用户角色(30 = Author)
syncRolesbooleanfalse每次登录时根据 IdP 组更新角色
syncNamebooleantrue每次已认证请求时,从 IdP 更新用户名称
roleMappingobject—将 IdP 组名映射到角色级别
audienceEnvVarstring"CF_ACCESS_AUDIENCE"包含 audience 标签的环境变量。省略 audience 时使用。

请提供 audience,或在 audienceEnvVar 下提供环境值。

角色映射

将 IdP 组映射到 EmDash 角色:

emdash({
	auth: access({
		teamDomain: "myteam.cloudflareaccess.com",
		audienceEnvVar: "CF_ACCESS_AUDIENCE",
		roleMapping: {
			Admins: 50, // Admin
			"Content Editors": 40, // Editor
			Writers: 30, // Author
		},
		defaultRole: 20, // Contributor for users not in any group
	}),
});

若用户属于多个组,第一个匹配的组生效。访问站点的首个用户无论组如何都始终成为 Admin。

角色同步行为

默认(syncRoles: false)下,用户角色在首次登录时设定,之后不会改变。这允许管理员在 EmDash 中手动调整角色。

若希望 IdP 组具有权威性,请设置 syncRoles: true——用户角色会在每次登录时根据当前组更新。

名称同步行为

默认情况下(syncName: true),EmDash 会在每次已认证请求时,用 Access 中的名称替换用户的名称。如果 Access 身份没有名称,EmDash 会使用电子邮件地址中 @ 之前的部分。管理后台的用户编辑器会以只读方式显示名称,并提示在 Access 中修改。

设置 syncName: false 可让名称在 EmDash 中保持可编辑。此时,Access 中的名称仅在用户首次预配时使用。

请求与会话流程

  1. 用户访问受 Access 应用保护的路径。
  2. 若不存在 Access 会话,Cloudflare Access 将用户重定向到身份提供方。
  3. 认证后,Access 在 Cf-Access-Jwt-Assertion 中向源站发送已签名 JWT。
  4. EmDash 验证令牌的签名、签发者与 audience,然后读取 Access 身份与组。
  5. EmDash 查找或预配本地用户,应用配置的角色行为,并将用户记录到 Astro 会话。
  6. 之后对受保护 EmDash 路由的请求会重复 Access 验证。公开页面可使用 EmDash 会话识别用户,而无需将其视为新的 Access 请求证明。

被 Access 取代的功能

启用 Access 后,这些功能不可用:

  • 登录页(/_emdash/admin/login)
  • 通行密钥注册与管理
  • GitHub、Google、Microsoft 与 Atmosphere 登录
  • 魔法链接登录
  • 自助注册
  • 用户邀请

Access 策略决定谁能到达 EmDash。EmDash 仍拥有本地角色、内容所有权与禁用用户标志。在 syncRoles: false 时,管理员可在 EmDash 中更改已预配用户的角色。在 syncRoles: true 时,映射的 Access 组会在每次登录时替换该角色。用户名称通过 syncName 遵循相同的模式,只是它默认为 true。

故障排除

”No Access JWT present”

请求在没有 Access JWT 的情况下到达 EmDash。这意味着:

  • Access 未配置为保护您的应用
  • Access 策略未匹配管理路由

请确认 Access 应用覆盖完整的 /_emdash/* 路径,且其策略包含该用户。

“JWT audience mismatch”

配置中的 audience 与 JWT 不匹配。请再次核对 Access 应用设置中的 Application Audience Tag。

“User not authorized”

用户已通过 Access 认证,但 autoProvision 为 false 且 EmDash 中不存在该用户。请:

  • 设置 autoProvision: true,或
  • 在其登录前手动创建用户