ガイド 第38

認証と認可

shared-password realms・Better Auth 昇格パス

@event/core@event/shell@event/fours-sdk

現状の実装は 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 = constantTimeEqualspikes の平文 === を廃し timing 攻撃を鈍らせる
失敗遅延loginJitter()(100–250ms)存在判別・総当たりを鈍らせる(呼び出し側で await)
dev バイパスdevBypass 既定 true かつ NODE_ENV=development のみVercel / next start は常に production 判定で本番には効かない
未設定パスワード空なら isAuthedFromValue は常に falsefail-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-sdkverify4SUser(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.tsROLE_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 で始め、必要になったら昇格する——という方針は正だが、昇格プロバイダ自体はまだ書かれていない。ここを実装済みと誤認しないこと。