現状の実装は shared-password 一択。閉じたチーム向けの共有パスワード cookie 認証を、@event/core(pure な照合関数)と @event/shell(realm 宣言 + middleware + dev ログイン)で組む。章題の「Better Auth 昇格パス」は 設計のみで未実装(後述で正直に線引きする)。
createSharedPasswordAuth — 照合の pure 関数(@event/core)
cookie の読み書きはアプリ側(next/headers)。パッケージは値を受け取って照合するだけで next 非依存を保つ:
// apps/starter/src/lib/adminAuth.ts(実物)
import { createSharedPasswordAuth } from "@event/core";
export const adminAuth = createSharedPasswordAuth({
cookieName: "starter_admin_session", // イベントごとに一意
});
// adminAuth.verifyPassword(candidate) … ログイン照合(constant-time)
// adminAuth.isAuthedFromValue(cookieValue) … リクエスト毎の cookie 照合
// adminAuth.isConfigured() … 未設定は fail-closed(false)
要点(3実装の分岐をここで統一):
| 項目 | 実装 | 効果 |
|---|---|---|
| ログイン照合 | verifyPassword = constantTimeEqual | spikes の平文 === を廃し timing 攻撃を鈍らせる |
| 失敗遅延 | loginJitter()(100–250ms) | 存在判別・総当たりを鈍らせる(呼び出し側で await) |
| dev バイパス | devBypass 既定 true かつ NODE_ENV=development のみ | Vercel / next start は常に production 判定で本番には効かない |
| 未設定 | パスワード空なら isAuthedFromValue は常に false | fail-closed |
| cookie 属性 | httpOnly / sameSite:"lax" / secure(prod) / maxAge 既定7日 | — |
constantTimeEqual は文字単位 XOR の依存なし実装で、middleware(edge runtime)から使うため @event/core/auth subpath でも公開される。
defineRealms — N 個の named realm(@event/shell)
admin だけと決め打たず、realm を宣言して cookie 名 / パスワード env / ガードを一括生成する(xdeal は admin / roster を別々に手書きしていた):
import { defineRealms } from "@event/shell";
export const realms = defineRealms({
admin: { cookieName: "ev_admin_session", passwordEnv: "ADMIN_PASSWORD" },
roster: { cookieName: "ev_roster_session", passwordEnv: "ROSTER_PASSWORD" },
});
// realms.admin は SharedPasswordAuth を継承しつつ requireFromValue を足す:
// realms.admin.requireFromValue(cookieValue) // 不一致なら throw "unauthorized (realm: admin)"
middleware は「境界」・server 側で必ず再検査
統治原則(CVE-2025-29927): middleware は境界であって認可ではない。 createAuthGateMiddleware は edge で cookie を弾くだけ(barrel でなく @event/core/auth subpath から constantTimeEqual を import し、node:fs/crypto を edge バンドルに混ぜない)。認可の再検査は各 server action / route が requireFromValue(realm ガード)で必ず行う。 middleware 単独に認可を委ねない。
createDevLoginHandler — dev 専用ワンクリックログイン
devhub の「開く」ボタンが /login/dev?from=/admin を叩く導線。安全不変条件を verbatim で携行する: 本番は /login へ倒す / localhost 直アクセスのみ(cf-connecting-ip があれば Cloudflare 経由 = 拒否)/ open-redirect 防止(自サイト内絶対パスのみ)/ パスワードは URL を通らない(サーバが env を読んで cookie を張るだけ)。
4S ユーザー本人性(per-user identity)
shared-password は「管理者かどうか」の realm。登壇者・来場者本人の識別は別で、@event/fours-sdk の verify4SUser(idToken)(GET /users/me を Bearer で叩き、client 申告の name/avatar は無視して権威的に解決)+ createSessionCodec(jose 署名 cookie)/ isResourceOwner(ownership guard)が担う。共有パスワード realm とは直交する層である。
Better Auth 昇格パス — 設計のみ・未実装
章題の「Better Auth 昇格パス」は 現状コードに存在しない。docs/SPEC_INDEX.md / docs/ARCHITECTURE.md に、将来 strategy: 'shared-password' | 'better-auth' を defineEventShell で切り替え、閉じたチーム password を超えたイベントが org roles / 2FA / passkey(xdeal-lp の src/lib/auth.ts betterAuth config + dal.ts の ROLE_HIERARCHY)へ昇格する導線が構想されているが、@event/* パッケージ側は未実装。seam として fourSUserId(Better Auth アカウント ↔ 4S user の連結点)が設計に置かれているだけである。
- 実装済み: shared-password realms(core auth / shell realms・middleware・devLogin)、4S per-user verify + jose session
- 未実装(設計):
strategy: 'better-auth'プロバイダ、org RBAC / DAL ガード、shared-password → better-authの昇格導線
新イベントは shared-password で始め、必要になったら昇格する——という方針は正だが、昇格プロバイダ自体はまだ書かれていない。ここを実装済みと誤認しないこと。