身份验证

本页内容

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

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

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

选择认证模式

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

通行密钥是默认方式。GitHub、Google 与 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 随 EmDash 附带。Atmosphere 与第三方提供方是通过同一接口注册的独立包。

提供方是叠加的——启用提供方后通行密钥仍然可用。仅当提供方给出相同的已验证电子邮件地址时,GitHub 与 Google 才会自动关联现有 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 先检查带前缀的名称,再回退到无前缀名称:

VariablePurpose
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 先检查带前缀的名称,再回退到无前缀名称:

VariablePurpose
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。

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 使用五级基于角色的访问控制:

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

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

订阅者与草稿内容

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

邀请用户

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

  1. 前往 Settings > 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 与 Atmosphere 提供方也可以创建初始管理员账户。

会话

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

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

认证速率限制

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

EndpointLimit
POST /_emdash/api/auth/passkey/options每分钟 10 次请求
POST /_emdash/api/auth/magic-link/send每 5 分钟 3 次请求
POST /_emdash/api/auth/signup/request每 5 分钟 3 次请求

在 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 应用的令牌会被拒绝。

配置选项

OptionTypeDefaultDescription
teamDomainstringrequired您的 Access 团队域名(例如 myteam.cloudflareaccess.com)
audiencestring—直接提供的 Application Audience (AUD) 标签。在 Workers 上优先使用 audienceEnvVar。
autoProvisionbooleantrue首次 Access 登录时创建 EmDash 用户
defaultRolenumber30未匹配任何组的用户角色(30 = Author)
syncRolesbooleanfalse每次登录时根据 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——用户角色会在每次登录时根据当前组更新。

请求与会话流程

  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 与 Atmosphere 登录
  • 魔法链接登录
  • 自助注册
  • 用户邀请

Access 策略决定谁能到达 EmDash。EmDash 仍拥有本地角色、内容所有权与禁用用户标志。在 syncRoles: false 时,管理员可在 EmDash 中更改已预配用户的角色。在 syncRoles: true 时,映射的 Access 组会在每次登录时替换该角色。

故障排除

”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,或
  • 在其登录前手动创建用户