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,或 - 在其登入前手動建立使用者