認証

このページ

EmDash はパスキー認証を主要なログイン方法として採用しています。パスキーはフィッシング耐性があり、パスワードが不要で、ブラウザやパスワードマネージャーを通じてデバイス間で利用できます。

Cloudflare へのデプロイでは、代替の認証プロバイダーとして Cloudflare Access をオプションで使用できます。

仕組み

パスキーは WebAuthn(Web 標準)を使い、デバイスまたはパスワードマネージャーに保存される公開鍵クレデンシャルを作成します。ログイン時にデバイスがクレデンシャルの所有を証明し、パスワードがネットワーク上を流れることはありません。

パスキー認証の利点:

  • 覚えたり漏洩するパスワードがない
  • フィッシング耐性 — クレデンシャルはサイトのドメインに紐付く
  • デバイス間同期 — iCloud キーチェーン、Google パスワードマネージャー、1Password などと連携
  • 高速ログイン — 生体認証や PIN でワンタップ

初回ユーザーのセットアップ

管理画面に初めてアクセスすると、セットアップウィザードが管理者アカウントの作成を案内します。

  1. http://localhost:4321/_emdash/admin にアクセスします

  2. セットアップウィザードにリダイレクトされます。次を入力します:

    • Site Title — サイト名
    • Tagline — 短い説明
    • Admin Email — メールアドレス
  3. Create Site をクリックしてパスキーを登録します

  4. ブラウザがパスキーの作成を求めます:

    • macOS: Touch ID、デバイスパスワード、またはセキュリティキー
    • Windows: Windows Hello またはセキュリティキー
    • モバイル: Face ID、指紋、または PIN
  5. パスキーが登録されると、ログインされ管理ダッシュボードにリダイレクトされます。

ログイン

セットアップ後、管理画面に戻るとパスキー認証が求められます:

  1. /_emdash/admin にアクセスします

  2. ログインしていない場合はログインページが表示されます

  3. Sign in をクリックして認証します

  4. ブラウザがパスキー(生体認証、PIN、またはセキュリティキー)を求めます

  5. 検証後、管理ダッシュボードにリダイレクトされます

マジックリンクのフォールバック

パスキーが使えない場合(例: デバイス紛失)、マジックリンクが代替手段となります。メールの設定が必要です。

  1. ログインページで Sign in with email をクリックします

  2. メールアドレスを入力します

  3. 受信トレイでログインリンクを確認します

  4. リンクをクリックして認証します(15 分間有効)

OAuth ログイン

EmDash は設定により GitHub と Google の OAuth ログインをサポートします。ユーザーは初回パスキー設定後にアカウントをリンクできます。

設定手順は設定ガイドを参照してください。

ユーザーロール

EmDash は 5 段階のロールベースアクセス制御を使用します:

ロールレベル説明
Subscriber10閲覧のみ
Contributor20コンテンツ作成(承認が必要)
Author30自分のコンテンツの作成・編集・公開
Editor40すべてのコンテンツを管理
Admin50設定を含む全アクセス

各ロールはすべての下位レベルの権限を継承します。最初のユーザーは必ず Admin として作成されます。

ユーザーの招待

管理者は管理画面から新しいユーザーを招待できます:

  1. Settings > Users に移動します

  2. Invite User をクリックします

  3. ユーザーのメールアドレスを入力し、ロールを選択します

  4. Send Invite をクリックします

  5. ユーザーに招待リンクを含むメールが届きます

  6. ユーザーがリンクをクリックしてパスキーを登録します

招待は 7 日間有効です。管理者はユーザーページから招待の再送や取り消しができます。

パスキーの管理

ユーザーはアカウント設定からパスキーを管理できます:

  • パスキーの追加 — バックアップや他のデバイス用に追加のパスキーを登録
  • パスキーの削除 — 使わなくなったパスキーを削除
  • パスキーの名前変更 — パスキーにわかりやすい名前を付ける

各ユーザーは最大 10 個のパスキーを登録できます。

セルフサインアップ

チームサイトでは、特定のメールドメインのセルフサインアップを有効にできます:

import { defineConfig } from "astro/config";
import emdash from "emdash/astro";

export default defineConfig({
	integrations: [
		emdash({
			auth: {
				selfSignup: {
					domains: ["example.com"],
					defaultRole: "contributor",
				},
			},
		}),
	],
});

一致するメールドメインのユーザーは、招待なしでサインアップできます。確認メールを受け取り、パスキーを登録してサインアップを完了します。

セッション設定

セッションは安全な HttpOnly Cookie を使い、適切なデフォルト値が設定されています:

emdash({
	auth: {
		session: {
			maxAge: 30 * 24 * 60 * 60, // 30 日(デフォルト)
			sliding: true, // アクティビティ時に有効期限をリセット
		},
	},
});

セキュリティに関する注意

  • パスキーは公開鍵として保存 — 秘密鍵がデバイスから出ることはない
  • チャレンジ検証でリプレイ攻撃を防止
  • レート制限でブルートフォースを防御(5 回/分/IP)
  • セッションは HttpOnly, Secure, SameSite=Lax でクッキーのセキュリティを確保
  • マジックリンクトークンは SHA-256 ハッシュ化 — 生のトークンは保存されない

トラブルシューティング

「No passkeys registered」

ログイン時にこのエラーが表示される場合、パスキーがパスワードマネージャーから削除された可能性があります。管理者にマジックリンクか新しい招待の送信を依頼してください。

「Passkey authentication failed」

通常、パスキーが別のドメインで作成されたことを意味します。パスキーはドメインに紐付いており、localhost:4321 用のパスキーは example.com では使えません。各ドメインで新しいパスキーを登録してください。

「Session expired」

セッションはデフォルトでスライディング有効期限付きの 30 日間です。予期せずログアウトされた場合は、Cookie をクリアして再ログインしてください。

すべてのパスキーを紛失した場合

登録したすべてのパスキーにアクセスできなくなった場合:

  1. 別の管理者にマジックリンクの送信を依頼(メール設定が必要)
  2. マジックリンクでログイン
  3. アカウント設定で新しいパスキーを登録

唯一の管理者でメールが未設定の場合は、データベースを通じてサイトの認証をリセットする必要があります。

Cloudflare Access

Cloudflare にデプロイする場合、パスキーの代わりに Cloudflare Access を認証プロバイダーとして使えます。Access は既存の ID プロバイダーを使ってエッジで認証を処理します。

Cloudflare Access を使う理由

  • シングルサインオン — 企業の IdP でユーザーが認証
  • 一元的なアクセス制御 — Cloudflare ダッシュボードで管理画面へのアクセスを管理
  • パスキー管理が不要 — パスキーの登録や管理が不要
  • グループベースのロール — IdP グループを EmDash ロールに自動マッピング

セットアップ

  1. EmDash サイト用の Cloudflare Access アプリケーションを作成
  2. アプリケーション設定から Application Audience (AUD) Tag をメモ
  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...", // Access アプリの設定から
			}),
		}),
	],
});

設定オプション

オプションデフォルト説明
teamDomainstring必須Access チームドメイン(例: myteam.cloudflareaccess.com
audiencestring必須Access 設定の Application Audience (AUD) タグ
autoProvisionbooleantrue初回 Access ログイン時に EmDash ユーザーを作成
defaultRolenumber30どのグループにも一致しないユーザーのロール(30 = Author)
syncRolesbooleanfalseログインごとに IdP グループに基づいてロールを更新
roleMappingobjectIdP グループ名をロールレベルにマッピング
audienceEnvVarstring"CF_ACCESS_AUDIENCE"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
	}),
});

ユーザーが複数のグループに属する場合、最初に一致したグループが適用されます。サイトに最初にアクセスした最初のユーザーは、グループに関係なく常に Admin になります。

ロール同期の動作

デフォルト(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 ポリシーを通じて完全に行います。

トラブルシューティング

「No Access JWT present」

リクエストが Access JWT なしで EmDash に到達しました。次の原因が考えられます:

  • Access がアプリケーションを保護するよう設定されていない
  • Access ポリシーが管理ルートに一致していない

Access アプリケーションが /_emdash/admin/* をカバーしていることを確認してください。

「JWT audience mismatch」

設定の audience が JWT と一致しません。Access アプリケーション設定の Application Audience Tag を再確認してください。

「User not authorized」

ユーザーは Access で認証されましたが、autoProvisionfalse で EmDash にユーザーが存在しません。次のいずれかを行ってください:

  • autoProvision: true を設定する
  • ログイン前にユーザーを手動で作成する