認証

このページ

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

パスキーに加えて、プラグイン可能なログインプロバイダーを追加できます。GitHub と Google は EmDash に含まれています。別途インストールする Atmosphere プロバイダー は AT Protocol アカウントを追加し、同じプロバイダーインターフェースは他のパッケージにも開かれています。文書化された GitHub、Google、Atmosphere プロバイダーは、最初の管理者アカウントを作成するか、リンクされた EmDash ユーザーでサインインできます。

Cloudflare デプロイでは、Cloudflare Access は本番環境で別の排他的な認証モードです。EmDash のログイン方法を表示するのではなく、保護された EmDash ルートで Access の資格情報を検証します。

認証モードを選ぶ

パスキーは WebAuthn を使用します。これはデバイスに保存されるかパスワードマネージャー経由で同期される公開鍵クレデンシャルを作成する Web 標準です。ログイン時、デバイスはネットワーク上にパスワードを送ることなく、クレデンシャルの所持を証明します。

パスキーがデフォルトです。GitHub、Google、Atmosphere プロバイダーは追加のログイン方法です。それぞれがユーザーを認証し、EmDash アカウントをリンクまたは作成し、パスキーログインと同じ EmDash セッションを確立します。

パスキー認証は次を提供します:

  • 記憶したり漏洩したりするパスワードがない
  • フィッシング耐性 — クレデンシャルはサイトのドメインに紐づけられる
  • クロスデバイス同期 — iCloud Keychain、Google Password Manager、1Password などに対応
  • 高速ログイン — 生体認証または PIN でワンタップ

Cloudflare Access は authProviders の代わりに auth オプションを使用します。本番環境では、保護された /_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()],
		}),
	],
});

ログインページでは順序が重要です。プロバイダーはリストした順にレンダリングされ、コンパクトなボタンのみのプロバイダーが先、カスタムフォームが必要なプロバイダー(ハンドルを求める 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

次の例はハンドル許可リスト付きで Atmosphere プロバイダーを有効にします:

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

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

クライアントシークレットや環境変数は不要です。ハンドル/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 は 5 レベルのロールベースアクセス制御を使用します:

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 プロバイダーは初期管理者アカウントも作成できます。

セッション

パスキー、マジックリンク、招待、ログインプロバイダーのコールバックは、Astro のセッションストアに EmDash ユーザー ID を保存します。ブラウザは 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/options1 分あたり 10 リクエスト
POST /_emdash/api/auth/magic-link/send5 分あたり 3 リクエスト
POST /_emdash/api/auth/signup/request5 分あたり 3 リクエスト

Cloudflare では、EmDash は Cloudflare のリクエストメタデータからクライアント IP を読み取ります。リバースプロキシ背後のセルフホストサイトは、EmDash がプロキシのクライアント IP ヘッダーを使う前に trustedProxyHeaders を設定する必要があります。信頼できる 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/* だけを保護すると、EmDash が期待する JWT なしで REST API が残ります。
  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
teamDomainstringrequiredAccess チームドメイン(例: 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 は署名済み JWT を Cf-Access-Jwt-Assertion でオリジンに送ります。
  4. EmDash はトークンの署名、発行者、audience を検証し、Access のアイデンティティとグループを読み取ります。
  5. EmDash はローカルユーザーを検索またはプロビジョニングし、設定されたロール動作を適用し、Astro セッションにユーザーを記録します。
  6. 保護された EmDash ルートへの後続リクエストは Access 検証を繰り返します。公開ページは、新しい Access リクエストの証明として扱わずに EmDash セッションでユーザーを識別できます。

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 を設定する、または
  • ログイン前に手動でユーザーを作成する