驗證

本頁內容

EmDash 使用通行密鑰驗證作為其主要登入方式。通行密鑰具有抗網路釣魚能力,無需密碼,並可透過瀏覽器或密碼管理器在裝置間工作。

除了通行密鑰之外,您還可以新增可插拔的登入提供商 — GitHub、Google 和 Atmosphere(AT 協定)開箱即用,且相同的提供商介面對第三方套件開放。任何設定的提供商都可用於建立第一個管理員帳戶、登入或連結到現有使用者。

對於 Cloudflare 部署,Cloudflare Access 也可作為接管整個登入流程的獨占驗證方法。

運作原理

通行密鑰使用 WebAuthn,這是一個建立公鑰憑證的 Web 標準,憑證儲存在您的裝置上或透過密碼管理器同步。當您登入時,您的裝置證明擁有憑證,而無需透過網路傳送密碼。

通行密鑰驗證的優勢:

  • 無需記住或洩露密碼
  • 抗網路釣魚 — 憑證繫結到您網站的網域
  • 跨裝置同步 — 可與 iCloud 鑰匙圈、Google 密碼管理器、1Password 等搭配使用
  • 快速登入 — 使用生物辨識或 PIN 一鍵登入

首次使用者設定

首次存取管理面板時,設定精靈將引導您建立管理員帳戶。

  1. 導覽至 http://localhost:4321/_emdash/admin

  2. 您將被重新導向到設定精靈。輸入:

    • 網站標題 — 您網站的名稱
    • 標語 — 簡短描述
    • 管理員信箱 — 您的信箱地址
  3. 點擊建立網站以註冊您的通行密鑰

  4. 您的瀏覽器將提示您建立通行密鑰:

    • macOS:Touch ID、裝置密碼或安全金鑰
    • Windows:Windows Hello 或安全金鑰
    • 行動裝置:Face ID、指紋或 PIN
  5. 註冊通行密鑰後,您將登入並重新導向到管理儀表板。

登入

設定完成後,返回管理面板將觸發通行密鑰驗證:

  1. 造訪 /_emdash/admin

  2. 如果未登入,您將看到登入頁面

  3. 點擊登入進行驗證

  4. 您的瀏覽器會提示您提供通行密鑰(生物辨識、PIN 或安全金鑰)

  5. 驗證後,您將被重新導向到管理儀表板

魔法連結備用方案

如果您無法使用通行密鑰(例如,裝置遺失),魔法連結提供了替代方案。這需要設定信箱。

  1. 在登入頁面上,點擊使用信箱登入

  2. 輸入您的信箱地址

  3. 檢查收件匣中的登入連結

  4. 點擊連結進行驗證(有效期 15 分鐘)

登入提供商

除了通行密鑰之外,EmDash 還支援可插拔的登入提供商,它們會出現在登入頁面和設定精靈中。GitHub、Google 和 Atmosphere 提供商開箱即用;第三方套件可以使用相同的介面註冊自己的提供商。

提供商是累加的 — 啟用提供商時通行密鑰繼續工作,使用者可以將提供商連結到現有的僅通行密鑰帳戶。第一個使用者也可以透過任何設定的提供商建立,因此如果您願意,全新安裝可以完全跳過通行密鑰。

設定提供商

將提供商傳遞給 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()],
		}),
	],
});

對於登入頁面,順序很重要:提供商按您列出的順序渲染,緊湊的純按鈕提供商首先顯示,需要自訂表單的提供商(如要求提供使用者名稱的 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

Atmosphere(AT 協定)

對於貢獻者已經擁有 Atmosphere 帳戶的網站 — Bluesky 和更廣泛的 AT 協定網路背後的使用者擁有的身分 — 安裝 Atmosphere 提供商:

pnpm add @emdash-cms/auth-atproto

以下範例啟用帶有使用者名稱白名單的 Atmosphere 提供商:

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

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

無需用戶端密鑰或環境變數。有關使用者名稱/DID 白名單、角色對映以及 AT 協定 OAuth 設定檔所需的本地開發設定,請參閱 Atmosphere 登入指南

建構您自己的提供商

提供商只是一個 AuthProviderDescriptor — 一個 id、一個人類可讀的標籤,以及管理端 React 元件、路由處理程序、公共路由前綴和儲存集合的任意組合。該形狀從 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完全存取權限,包括設定

每個角色都繼承所有較低級別的權限。第一個使用者始終建立為管理員。

訂閱者和草稿內容

訂閱者擁有 content:read 權限,因此可以向已驗證的讀者提供僅限會員的已發布內容。他們無法看到草稿、已排程項目、已刪除項目、修訂版本或預覽 URL — 這些由 content:read_drafts 保護,授予給貢獻者及以上角色。清單和取得端點對訂閱者透明地過濾為 status=published;僅編輯者檢視(/compare/revisions/trash/preview-url)直接拒絕訂閱者請求。

邀請使用者

管理員可以透過管理面板邀請新使用者:

  1. 前往設定 > 使用者

  2. 點擊邀請使用者

  3. 輸入使用者的信箱並選擇角色

  4. 點擊傳送邀請

  5. 使用者會收到帶有邀請連結的郵件

  6. 他們點擊連結並註冊通行密鑰

邀請有效期為 7 天。管理員可以從使用者頁面重新傳送或撤銷邀請。

管理通行密鑰

使用者可以從帳戶設定中管理他們的通行密鑰:

  • 新增通行密鑰 — 為備份或其他裝置註冊額外的通行密鑰
  • 刪除通行密鑰 — 刪除您不再使用的通行密鑰
  • 重新命名通行密鑰 — 為通行密鑰提供描述性名稱

每個使用者最多可以註冊 10 個通行密鑰。

允許群組無需邀請即可登入

要允許群組無需邀請每個使用者即可登入,請設定帶有白名單的登入提供商。Atmosphere 提供商接受 allowedHandlesallowedDIDs(參見 Atmosphere 登入);Cloudflare Access 配接器透過 autoProvisionroleMapping 從您的身分提供商佈建使用者。任何設定的提供商也可以建立初始管理員帳戶。

工作階段

工作階段使用安全的 HttpOnly、SameSite=Lax cookie,並具有滑動過期時間,持續 30 天 — 過期時間在活動時重設。

安全性說明

  • 通行密鑰儲存為公鑰 — 私鑰永遠不會離開您的裝置
  • 挑戰驗證可防止重播攻擊
  • 速率限制可防止暴力破解(5 次嘗試/分鐘/IP)
  • 工作階段是 HttpOnly、Secure、SameSite=Lax 以確保 cookie 安全
  • 魔法連結權杖採用 SHA-256 雜湊 — 原始權杖永遠不會儲存

疑難排解

”未註冊通行密鑰”

如果您在登入時看到此錯誤,您的通行密鑰可能已從密碼管理器中刪除。請管理員向您傳送魔法連結或新邀請。

“通行密鑰驗證失敗”

這通常意味著通行密鑰是為不同的網域建立的。通行密鑰與網域繫結 — 為 localhost:4321 建立的通行密鑰在 example.com 上無法使用。為每個網域註冊新的通行密鑰。

“工作階段已過期”

工作階段預設持續 30 天,具有滑動過期時間。如果您意外登出,請清除 cookie 並重新登入。

遺失所有通行密鑰

如果您失去了對所有已註冊通行密鑰的存取權限:

  1. 請另一位管理員向您傳送魔法連結(需要信箱設定)
  2. 使用魔法連結登入
  3. 在帳戶設定中註冊新的通行密鑰

如果您是唯一的管理員且未設定信箱,則需要透過資料庫重設網站的驗證。

Cloudflare Access

部署到 Cloudflare 時,您可以使用 Cloudflare Access 作為您的驗證提供商,而不是通行密鑰。Access 使用您現有的身分提供商在邊緣處理驗證。

為什麼使用 Cloudflare Access

  • 單一登入 — 使用者使用您公司的 IdP 進行驗證
  • 集中存取控制 — 在 Cloudflare 儀表板中管理誰可以存取管理
  • 無需管理通行密鑰 — 無需註冊或管理通行密鑰
  • 基於群組的角色 — 自動將 IdP 群組對映到 EmDash 角色

設定

  1. 為您的 EmDash 網站建立 Cloudflare Access 應用程式
  2. 從應用程式設定中記下應用程式受眾(AUD)標籤
  3. 設定 EmDash 以使用 Access:
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",
				audience: "abc123def456...", // From Access app settings
			}),
		}),
	],
});

設定選項

選項型別預設值描述
teamDomainstring必需您的 Access 團隊網域(例如 myteam.cloudflareaccess.com
audiencestring必需Access 設定中的應用程式受眾(AUD)標籤
autoProvisionbooleantrue在首次 Access 登入時建立 EmDash 使用者
defaultRolenumber30不符合任何群組的使用者的角色(30 = Author)
syncRolesbooleanfalse根據 IdP 群組在每次登入時更新角色
roleMappingobject將 IdP 群組名稱對映到角色級別
audienceEnvVarstring"CF_ACCESS_AUDIENCE"受眾標籤的環境變數名稱(硬編碼的替代方案)

角色對映

將您的 IdP 群組對映到 EmDash 角色:

emdash({
	auth: access({
		teamDomain: "myteam.cloudflareaccess.com",
		audience: "abc123...",
		roleMapping: {
			Admins: 50, // Admin
			"Content Editors": 40, // Editor
			Writers: 30, // Author
		},
		defaultRole: 20, // 不屬於任何群組的使用者為 Contributor
	}),
});

如果使用者屬於多個群組,則第一個符合的群組獲勝。存取網站的第一個使用者始終成為管理員,無論群組如何。

角色同步行為

預設情況下(syncRoles: false),使用者的角色在首次登入時設定,之後不會變更。這允許管理員在 EmDash 中手動調整角色。

如果您希望 IdP 群組具有權威性,請設定 syncRoles: true — 使用者的角色將根據其當前群組在每次登入時更新。

運作原理

  1. 使用者造訪 /_emdash/admin
  2. Cloudflare Access 攔截並重新導向到您的 IdP
  3. 使用者進行驗證(SSO、MFA 等)
  4. Access 在請求中設定簽署的 JWT
  5. EmDash 驗證 JWT 並建立/驗證使用者

已停用的功能

啟用 Access 後,以下功能不可用:

  • 登入頁面(/_emdash/admin/login
  • 通行密鑰註冊和管理
  • OAuth 登入
  • 魔法連結登入
  • 自助註冊
  • 使用者邀請

使用者管理完全透過您的 Cloudflare Access 原則完成。

疑難排解

”無 Access JWT”

請求在沒有 Access JWT 的情況下到達 EmDash。這意味著:

  • Access 未設定為保護您的應用程式
  • Access 原則與管理路由不符合

驗證您的 Access 應用程式涵蓋 /_emdash/admin/*

“JWT 受眾不符合”

您設定中的 audience 與 JWT 不符合。仔細檢查 Access 應用程式設定中的應用程式受眾標籤。

“使用者未授權”

使用者透過 Access 驗證,但 autoProvisionfalse,並且他們在 EmDash 中不存在。要麼:

  • 設定 autoProvision: true,要麼
  • 在他們登入之前手動建立使用者