EmDashは、パスキー認証を主要なログイン方法として使用します。パスキーはフィッシングに強く、パスワードを必要とせず、ブラウザやパスワードマネージャーを通じてデバイス間で動作します。
パスキーを超えて、プラガブルログインプロバイダーを追加できます — GitHub、Google、Atmosphere(ATプロトコル)はすぐに利用でき、同じプロバイダーインターフェースはサードパーティパッケージに開かれています。設定されたプロバイダーは、最初の管理者アカウントの作成、ログイン、または既存のユーザーへのリンクに使用できます。
Cloudflareデプロイメントの場合、Cloudflare Accessもログインフロー全体を引き継ぐ排他的な認証方法として利用できます。
仕組み
パスキーはWebAuthnを使用します。これは、デバイスに保存されるか、パスワードマネージャーを通じて同期される公開鍵資格情報を作成するWeb標準です。ログインすると、デバイスはネットワーク経由でパスワードを送信することなく、資格情報の所有を証明します。
パスキー認証の利点:
- 覚えるパスワードや漏洩するパスワードがない
- フィッシングに強い — 資格情報はサイトのドメインに紐付けられています
- クロスデバイス同期 — iCloudキーチェーン、Googleパスワードマネージャー、1Passwordなどと連携
- 高速ログイン — 生体認証またはPINで1タップ
最初のユーザー設定
管理パネルに初めてアクセスすると、セットアップウィザードが管理者アカウントの作成を案内します。
-
http://localhost:4321/_emdash/adminにアクセス -
セットアップウィザードにリダイレクトされます。以下を入力:
- サイトタイトル — サイトの名前
- タグライン — 短い説明
- 管理者メール — メールアドレス
-
サイトを作成をクリックしてパスキーを登録
-
ブラウザがパスキーの作成を促します:
- macOS: Touch ID、デバイスパスワード、またはセキュリティキー
- Windows: Windows Helloまたはセキュリティキー
- モバイル: Face ID、指紋、またはPIN
-
パスキーが登録されると、ログインして管理ダッシュボードにリダイレクトされます。
ログイン
セットアップ後、管理パネルに戻るとパスキー認証がトリガーされます:
-
/_emdash/adminにアクセス -
ログインしていない場合、ログインページが表示されます
-
サインインをクリックして認証
-
ブラウザがパスキーを要求します(生体認証、PIN、またはセキュリティキー)
-
検証後、管理ダッシュボードにリダイレクトされます
マジックリンクフォールバック
パスキーを使用できない場合(デバイスの紛失など)、マジックリンクが代替手段を提供します。これにはメールの設定が必要です。
-
ログインページでメールでサインインをクリック
-
メールアドレスを入力
-
受信トレイでログインリンクを確認
-
リンクをクリックして認証(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_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は最初に接頭辞付きの名前をチェックし、接頭辞なしのものにフォールバックします:
| 変数 | 目的 |
|---|---|
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プロトコル)
貢献者がすでに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は5つのレベルのロールベースアクセス制御を使用します:
| ロール | レベル | 説明 |
|---|---|---|
| Subscriber | 10 | 公開コンテンツを読む(下書きアクセスなし) |
| Contributor | 20 | コンテンツを作成(公開には承認が必要) |
| Author | 30 | 自分のコンテンツを作成/編集/公開 |
| Editor | 40 | すべてのコンテンツを管理 |
| Admin | 50 | 設定を含む完全なアクセス |
各ロールは、すべての下位レベルから権限を継承します。最初のユーザーは常に管理者として作成されます。
サブスクライバーと下書きコンテンツ
サブスクライバーはcontent:read権限を持っているため、メンバー限定の公開コンテンツを認証された読者に提供できます。下書き、予定項目、ゴミ箱アイテム、リビジョン、またはプレビューURLは表示できません — これらはcontent:read_draftsによって保護されており、コントリビューター以上に付与されます。リストと取得エンドポイントは、サブスクライバーに対して透過的にstatus=publishedにフィルタリングします。エディター専用ビュー(/compare、/revisions、/trash、/preview-url)は、サブスクライバーのリクエストを直接拒否します。
ユーザーを招待する
管理者は管理パネルから新しいユーザーを招待できます:
-
設定 > ユーザーに移動
-
ユーザーを招待をクリック
-
ユーザーのメールを入力し、ロールを選択
-
招待を送信をクリック
-
ユーザーは招待リンク付きのメールを受け取ります
-
リンクをクリックしてパスキーを登録
招待は7日間有効です。管理者はユーザーページから招待を再送信または取り消すことができます。
パスキーの管理
ユーザーはアカウント設定からパスキーを管理できます:
- パスキーを追加 — バックアップや他のデバイス用の追加パスキーを登録
- パスキーを削除 — 使用しなくなったパスキーを削除
- パスキーの名前を変更 — パスキーにわかりやすい名前を付ける
各ユーザーは最大10個のパスキーを登録できます。
招待なしでグループをサインインさせる
招待なしで各ユーザーをサインインさせるには、許可リスト付きのログインプロバイダーを設定します。AtmosphereプロバイダーはallowedHandlesとallowedDIDsを受け入れます(Atmosphereログインを参照)。Cloudflare Accessアダプターは、autoProvisionとroleMappingを介してIDプロバイダーからユーザーをプロビジョニングします。設定されたプロバイダーは、最初の管理者アカウントを作成することもできます。
セッション
セッションは安全なHttpOnly、SameSite=Laxクッキーを使用し、スライド式有効期限で30日間持続します — 有効期限はアクティビティ時にリセットされます。
セキュリティに関する注意事項
- パスキーは公開鍵として保存されます — 秘密鍵はデバイスを離れません
- チャレンジ検証はリプレイ攻撃を防ぎます
- レート制限はブルートフォースから保護します(5回の試行/分/IP)
- セッションはHttpOnly、Secure、SameSite=Laxですクッキーのセキュリティのため
- マジックリンクトークンはSHA-256でハッシュ化されます — 生のトークンは保存されません
トラブルシューティング
「パスキーが登録されていません」
ログイン時にこのエラーが表示される場合、パスキーがパスワードマネージャーから削除された可能性があります。管理者にマジックリンクまたは新しい招待を送信するよう依頼してください。
「パスキー認証に失敗しました」
これは通常、パスキーが別のドメイン用に作成されたことを意味します。パスキーはドメインに紐付けられています — localhost:4321のパスキーはexample.comでは機能しません。各ドメインに新しいパスキーを登録してください。
「セッションの有効期限が切れました」
セッションはデフォルトでスライド式有効期限で30日間持続します。予期せずログアウトされた場合は、クッキーをクリアして再度ログインしてください。
すべてのパスキーを紛失
登録済みのすべてのパスキーへのアクセスを失った場合:
- 別の管理者にマジックリンクを送信するよう依頼します(メール設定が必要)
- マジックリンクを使用してログイン
- アカウント設定で新しいパスキーを登録
唯一の管理者でメールが設定されていない場合は、データベースを通じてサイトの認証をリセットする必要があります。
Cloudflare Access
Cloudflareにデプロイする場合、パスキーの代わりにCloudflare Accessを認証プロバイダーとして使用できます。Accessは、既存のIDプロバイダーを使用してエッジで認証を処理します。
Cloudflare Accessを使用する理由
- シングルサインオン — ユーザーは会社のIdPで認証します
- 集中アクセス制御 — Cloudflareダッシュボードで管理者にアクセスできるユーザーを管理
- パスキー管理不要 — パスキーを登録または管理する必要がありません
- グループベースのロール — IdPグループをEmDashロールに自動的にマッピング
セットアップ
- EmDashサイト用のCloudflare Accessアプリケーションを作成
- アプリケーション設定からApplication Audience(AUD)タグをメモ
- Accessを使用するように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",
audience: "abc123def456...", // From Access app settings
}),
}),
],
});
設定オプション
| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
teamDomain | string | 必須 | Accessチームドメイン(例:myteam.cloudflareaccess.com) |
audience | string | 必須 | Access設定からのApplication Audience(AUD)タグ |
autoProvision | boolean | true | 最初のAccessログイン時にEmDashユーザーを作成 |
defaultRole | number | 30 | グループに一致しないユーザーのロール(30 = Author) |
syncRoles | boolean | false | IdPグループに基づいてログインごとにロールを更新 |
roleMapping | object | — | IdPグループ名をロールレベルにマップ |
audienceEnvVar | string | "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を設定してください — ユーザーのロールは現在のグループに基づいてログインごとに更新されます。
仕組み
- ユーザーが
/_emdash/adminにアクセス - Cloudflare AccessがインターセプトしてIdPにリダイレクト
- ユーザーが認証(SSO、MFAなど)
- Accessがリクエストに署名付きJWTを設定
- EmDashがJWTを検証し、ユーザーを作成/認証
無効化された機能
Accessが有効な場合、これらの機能は利用できません:
- ログインページ(
/_emdash/admin/login) - パスキーの登録と管理
- OAuthログイン
- マジックリンクログイン
- 自己登録
- ユーザー招待
ユーザー管理は完全にCloudflare Accessポリシーを通じて行われます。
トラブルシューティング
「Access JWTが存在しません」
リクエストがAccess JWTなしでEmDashに到達しました。これは以下を意味します:
- Accessがアプリケーションを保護するように設定されていない
- Accessポリシーが管理ルートに一致していない
Accessアプリケーションが/_emdash/admin/*をカバーしていることを確認してください。
「JWTオーディエンスの不一致」
設定のaudienceがJWTと一致しません。Accessアプリケーション設定でApplication Audience Tagを再確認してください。
「ユーザーが承認されていません」
ユーザーはAccessを介して認証されましたが、autoProvisionがfalseでEmDashに存在しません。次のいずれか:
autoProvision: trueを設定する、または- ログインする前にユーザーを手動で作成する