身分驗證

本頁內容

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