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 仍会存储本地用户,以便角色、所有权与禁用用户检查继续生效。
设置首个用户
首次访问管理面板时,设置向导会引导您创建管理员账户。
-
前往
http://localhost:4321/_emdash/admin -
在 Set up your site 中,输入站点标题与可选标语。模板也可以提供示例内容。选择 Continue。
-
在 Create your account 中,输入电子邮件地址与可选姓名。选择 Continue。
-
在 Secure your account 中,创建通行密钥或选择已配置的登录提供方之一。若选择通行密钥,浏览器会询问保存位置:
- macOS:Touch ID、设备密码或安全密钥
- Windows:Windows Hello 或安全密钥
- 移动端:Face ID、指纹或 PIN
-
完成浏览器或提供方流程。EmDash 将首个用户创建为 Admin 并打开仪表盘。
使用通行密钥登录
设置完成后,再次进入管理面板会触发通行密钥认证:
-
访问
/_emdash/admin -
若未登录,将看到登录页
-
点击 Sign in 进行认证
-
浏览器会提示通行密钥(生物识别、PIN 或安全密钥)
-
验证后,您将被重定向到管理仪表盘
使用魔法链接登录
若无法使用通行密钥,魔法链接可提供替代方案。站点必须先配置邮件提供方,EmDash 才能发送链接——参见邮件设置。
-
在登录页点击 Sign in with email
-
输入电子邮件地址
-
检查收件箱中的登录链接
-
点击链接(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 先检查带前缀的名称,再回退到无前缀名称:
| Variable | Purpose |
|---|---|
EMDASH_OAUTH_GITHUB_CLIENT_ID / GITHUB_CLIENT_ID | OAuth 应用客户端 ID |
EMDASH_OAUTH_GITHUB_CLIENT_SECRET / GITHUB_CLIENT_SECRET | OAuth 应用密钥 |
将 GitHub OAuth 应用的回调 URL 配置为 https://your-site.example.com/_emdash/api/auth/oauth/github/callback。
以下示例启用 Google 提供方:
import { google } from "emdash/auth/providers/google";
emdash({ authProviders: [google()] });
通过环境变量设置凭据。EmDash 先检查带前缀的名称,再回退到无前缀名称:
| Variable | Purpose |
|---|---|
EMDASH_OAUTH_GOOGLE_CLIENT_ID / GOOGLE_CLIENT_ID | OAuth 应用客户端 ID |
EMDASH_OAUTH_GOOGLE_CLIENT_SECRET / GOOGLE_CLIENT_SECRET | OAuth 应用密钥 |
将 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 使用五级基于角色的访问控制:
| Role | Level | Description |
|---|---|---|
| Subscriber | 10 | 阅读已发布内容(无草稿访问) |
| Contributor | 20 | 创建内容(发布需审批) |
| Author | 30 | 创建/编辑/发布自己的内容 |
| Editor | 40 | 管理全部内容 |
| Admin | 50 | 完全访问,包括设置 |
每个角色继承所有较低级别的权限。首个用户始终创建为 Admin。
订阅者与草稿内容
订阅者持有 content:read 权限,以便仅限成员的已发布内容可提供给已认证读者。他们无法查看草稿、定时项、回收站项、修订或预览 URL——这些由授予 Contributor 及以上的 content:read_drafts 控制。list 与 get 端点对订阅者透明地过滤为 status=published;仅编辑器视图(/compare、/revisions、/trash、/preview-url)会直接拒绝订阅者请求。
邀请用户
管理员可通过管理面板邀请新用户:
-
前往 Settings > Users
-
点击 Invite User
-
输入用户的电子邮件并选择角色
-
点击 Send Invite
-
若已配置邮件,EmDash 会发送邀请。否则,复制生成的链接并自行发给用户。
-
他们打开链接,并用邀请页提供的通行密钥或登录提供方创建账户。
邀请链接为一次性使用,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 分别生效:
| Endpoint | Limit |
|---|---|
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 上使用。请为每个域名注册新的通行密钥。
丢失全部通行密钥
若您失去了对所有已注册通行密钥的访问:
- 请另一位管理员发送恢复魔法链接。站点必须已配置邮件。
- 在 15 分钟内打开链接并选择 Continue 登录。
- 在账户设置中注册新的通行密钥。
若您是唯一管理员且未配置邮件,则需要通过数据库重置站点的认证。
Cloudflare Access
部署到 Cloudflare 时,可以使用 Cloudflare Access 替代内置登录方式。Access 在边缘用您的身份提供方认证用户。EmDash 验证已签名的 Access JWT,加载该人的身份与组,并将该身份映射到本地 EmDash 用户。
何时使用 Cloudflare Access
- 单点登录 — 用户通过公司 IdP 认证
- 集中访问控制 — 在 Cloudflare 仪表盘管理谁可访问管理端
- 无需管理通行密钥 — 不必注册或管理通行密钥
- 基于组的角色 — 自动将 IdP 组映射到 EmDash 角色
设置 Access
- 为站点的
/_emdash/*路径创建 Cloudflare Access 应用与策略。仅保护/_emdash/admin/*会使 REST API 缺少 EmDash 期望的 JWT。 - 复制应用的 Application Audience (AUD) Tag。
- 将标签存入运行时环境变量
CF_ACCESS_AUDIENCE。本地与已部署值请遵循 EmDash 密钥指南。 - 配置 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 应用的令牌会被拒绝。
配置选项
| Option | Type | Default | Description |
|---|---|---|---|
teamDomain | string | required | 您的 Access 团队域名(例如 myteam.cloudflareaccess.com) |
audience | string | — | 直接提供的 Application Audience (AUD) 标签。在 Workers 上优先使用 audienceEnvVar。 |
autoProvision | boolean | true | 首次 Access 登录时创建 EmDash 用户 |
defaultRole | number | 30 | 未匹配任何组的用户角色(30 = Author) |
syncRoles | boolean | false | 每次登录时根据 IdP 组更新角色 |
roleMapping | object | — | 将 IdP 组名映射到角色级别 |
audienceEnvVar | string | "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——用户角色会在每次登录时根据当前组更新。
请求与会话流程
- 用户访问受 Access 应用保护的路径。
- 若不存在 Access 会话,Cloudflare Access 将用户重定向到身份提供方。
- 认证后,Access 在
Cf-Access-Jwt-Assertion中向源站发送已签名 JWT。 - EmDash 验证令牌的签名、签发者与 audience,然后读取 Access 身份与组。
- EmDash 查找或预配本地用户,应用配置的角色行为,并将用户记录到 Astro 会话。
- 之后对受保护 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,或 - 在其登录前手动创建用户