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 はローカルユーザーを引き続き保存します。
最初のユーザーを設定する
管理パネルに初めてアクセスすると、セットアップウィザードが管理者アカウントの作成を案内します。
-
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()],
}),
],
});
ログインページでは順序が重要です。プロバイダーはリストした順にレンダリングされ、コンパクトなボタンのみのプロバイダーが先、カスタムフォームが必要なプロバイダー(ハンドルを求める 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
次の例はハンドル許可リスト付きで 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 レベルのロールベースアクセス制御を使用します:
| 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 プロバイダーは初期管理者アカウントも作成できます。
セッション
パスキー、マジックリンク、招待、ログインプロバイダーのコールバックは、Astro のセッションストアに EmDash ユーザー ID を保存します。ブラウザは 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 | 1 分あたり 10 リクエスト |
POST /_emdash/api/auth/magic-link/send | 5 分あたり 3 リクエスト |
POST /_emdash/api/auth/signup/request | 5 分あたり 3 リクエスト |
Cloudflare では、EmDash は Cloudflare のリクエストメタデータからクライアント IP を読み取ります。リバースプロキシ背後のセルフホストサイトは、EmDash がプロキシのクライアント IP ヘッダーを使う前に trustedProxyHeaders を設定する必要があります。信頼できる 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/*だけを保護すると、EmDash が期待する JWT なしで REST API が残ります。 - アプリケーションの 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 は署名済み JWT を
Cf-Access-Jwt-Assertionでオリジンに送ります。 - EmDash はトークンの署名、発行者、audience を検証し、Access のアイデンティティとグループを読み取ります。
- EmDash はローカルユーザーを検索またはプロビジョニングし、設定されたロール動作を適用し、Astro セッションにユーザーを記録します。
- 保護された 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を設定する、または- ログイン前に手動でユーザーを作成する