EmDash はパスキー認証を主要なログイン方法として採用しています。パスキーはフィッシング耐性があり、パスワードが不要で、ブラウザやパスワードマネージャーを通じてデバイス間で利用できます。
Cloudflare へのデプロイでは、代替の認証プロバイダーとして Cloudflare Access をオプションで使用できます。
仕組み
パスキーは WebAuthn(Web 標準)を使い、デバイスまたはパスワードマネージャーに保存される公開鍵クレデンシャルを作成します。ログイン時にデバイスがクレデンシャルの所有を証明し、パスワードがネットワーク上を流れることはありません。
パスキー認証の利点:
- 覚えたり漏洩するパスワードがない
- フィッシング耐性 — クレデンシャルはサイトのドメインに紐付く
- デバイス間同期 — iCloud キーチェーン、Google パスワードマネージャー、1Password などと連携
- 高速ログイン — 生体認証や PIN でワンタップ
初回ユーザーのセットアップ
管理画面に初めてアクセスすると、セットアップウィザードが管理者アカウントの作成を案内します。
-
http://localhost:4321/_emdash/adminにアクセスします -
セットアップウィザードにリダイレクトされます。次を入力します:
- Site Title — サイト名
- Tagline — 短い説明
- Admin Email — メールアドレス
-
Create Site をクリックしてパスキーを登録します
-
ブラウザがパスキーの作成を求めます:
- macOS: Touch ID、デバイスパスワード、またはセキュリティキー
- Windows: Windows Hello またはセキュリティキー
- モバイル: Face ID、指紋、または PIN
-
パスキーが登録されると、ログインされ管理ダッシュボードにリダイレクトされます。
ログイン
セットアップ後、管理画面に戻るとパスキー認証が求められます:
-
/_emdash/adminにアクセスします -
ログインしていない場合はログインページが表示されます
-
Sign in をクリックして認証します
-
ブラウザがパスキー(生体認証、PIN、またはセキュリティキー)を求めます
-
検証後、管理ダッシュボードにリダイレクトされます
マジックリンクのフォールバック
パスキーが使えない場合(例: デバイス紛失)、マジックリンクが代替手段となります。メールの設定が必要です。
-
ログインページで Sign in with email をクリックします
-
メールアドレスを入力します
-
受信トレイでログインリンクを確認します
-
リンクをクリックして認証します(15 分間有効)
OAuth ログイン
EmDash は設定により GitHub と Google の OAuth ログインをサポートします。ユーザーは初回パスキー設定後にアカウントをリンクできます。
設定手順は設定ガイドを参照してください。
ユーザーロール
EmDash は 5 段階のロールベースアクセス制御を使用します:
| ロール | レベル | 説明 |
|---|---|---|
| Subscriber | 10 | 閲覧のみ |
| Contributor | 20 | コンテンツ作成(承認が必要) |
| Author | 30 | 自分のコンテンツの作成・編集・公開 |
| Editor | 40 | すべてのコンテンツを管理 |
| Admin | 50 | 設定を含む全アクセス |
各ロールはすべての下位レベルの権限を継承します。最初のユーザーは必ず Admin として作成されます。
ユーザーの招待
管理者は管理画面から新しいユーザーを招待できます:
-
Settings > Users に移動します
-
Invite User をクリックします
-
ユーザーのメールアドレスを入力し、ロールを選択します
-
Send Invite をクリックします
-
ユーザーに招待リンクを含むメールが届きます
-
ユーザーがリンクをクリックしてパスキーを登録します
招待は 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 をクリアして再ログインしてください。
すべてのパスキーを紛失した場合
登録したすべてのパスキーにアクセスできなくなった場合:
- 別の管理者にマジックリンクの送信を依頼(メール設定が必要)
- マジックリンクでログイン
- アカウント設定で新しいパスキーを登録
唯一の管理者でメールが未設定の場合は、データベースを通じてサイトの認証をリセットする必要があります。
Cloudflare Access
Cloudflare にデプロイする場合、パスキーの代わりに Cloudflare Access を認証プロバイダーとして使えます。Access は既存の ID プロバイダーを使ってエッジで認証を処理します。
Cloudflare Access を使う理由
- シングルサインオン — 企業の IdP でユーザーが認証
- 一元的なアクセス制御 — Cloudflare ダッシュボードで管理画面へのアクセスを管理
- パスキー管理が不要 — パスキーの登録や管理が不要
- グループベースのロール — IdP グループを EmDash ロールに自動マッピング
セットアップ
- EmDash サイト用の Cloudflare Access アプリケーションを作成
- アプリケーション設定から Application Audience (AUD) Tag をメモ
- 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 アプリの設定から
}),
}),
],
});
設定オプション
| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
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" | 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 を設定してください — ログインごとに現在のグループに基づいてユーザーのロールが更新されます。
仕組み
- ユーザーが
/_emdash/adminにアクセス - Cloudflare Access がインターセプトし、IdP にリダイレクト
- ユーザーが認証(SSO、MFA など)
- Access が署名付き JWT をリクエストに設定
- 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 で認証されましたが、autoProvision が false で EmDash にユーザーが存在しません。次のいずれかを行ってください:
autoProvision: trueを設定する- ログイン前にユーザーを手動で作成する