## 再現(dev, 2026-06-28) TenantMember でログアウト → `admin@example.com`(APP_ADMIN)でログイン → **通知設定画面**で: ``` ユーザー情報の取得に失敗しました: 403 {"status":403,"code":"E_FORBIDDEN","message":"指定テナントのメンバーではありません","path":"/api/auth/me"} ``` ## 原因(推定) 1. **ログアウトで active tenant(`sessionStorage.tenantId`)がクリアされない**。前 member セッションの `tenantId=1` が残り、次ユーザー(admin、tenant1 非所属)のリクエストに `X-Tenant-Id: 1` が付与される(`Api._fetch`)。 2. **ログアウト→ログインが直前ページに戻る**(`Auth.logout()` の `redirectUri = origin + pathname`)。APP_ADMIN が `admin.html` ではなく**テナントスコープのメンバー画面(notification-settings.html)に着地**。 3. `notification-settings.js` は `isAppAdmin` で弾かず `/api/auth/me` を呼ぶ。`/api/auth/me` は設計上「X-Tenant-Id 不要枠」(基本設計書 §721)だが、`TenantContextFilter` は**ヘッダが存在すると membership 検証**するため、stale `X-Tenant-Id:1` で 403(admin は tenant1 非所属)。 ## 影響 - ユーザー切替時に**前ユーザーのテナントコンテキストが漏れる**。エラー(本件)に加え、兼務ユーザー等では想定外テナントでの操作につながり得る。**active tenant のクリアはログアウト時に必須**(セキュリティ観点)。 ## 対処案 - `Auth.logout()` で `Api.setTenantId(null)`(`sessionStorage.tenantId` クリア)。ログイン成功直後も `/me` 解決前に一旦クリアする。 - 「X-Tenant-Id 不要枠」(`/api/auth/me`・`/api/auth/tenants/{id}/select`・`/api/tenants` 等)は、**ヘッダがあっても membership 検証せず素通り**にする(設計 §721 と整合)。または該当 API 呼び出し時はクライアントが X-Tenant-Id を送らない。 - **役割不適合ページ着地のガード**: 各画面 `main()` 冒頭で APP_ADMIN を検出したら `admin.html` へ誘導(現状 `index.html` のみ実施)。`logout` の `redirectUri` を固定の入口にする案も。 ## 補足(レルム設計の確認) - Keycloak レルムは **`tasks` 単一**。SaaS Admin は別レルムではなく **realm ロール `APP_ADMIN`** で区別(基本設計書 §6.2 / ADR-0006)。「別レルムでは?」という違和感の正体は本バグ(セッション/テナントコンテキストの混線)であり、レルム構成自体は単一で設計どおり。 - 参考: SaaS Admin を別レルム/別 IdP に分離して権限境界を物理分離する案は、防御多層化の観点では検討余地あり(現設計は単一レルム + ロールの簡素化を意図)。別途要否を判断。 ## 関連 - #812(admin 画面シェルの露出)。役割ガード強化と併せて検討可。
再現(dev, 2026-06-28)
TenantMember でログアウト →
admin@example.com(APP_ADMIN)でログイン → 通知設定画面で:原因(推定)
sessionStorage.tenantId)がクリアされない。前 member セッションのtenantId=1が残り、次ユーザー(admin、tenant1 非所属)のリクエストにX-Tenant-Id: 1が付与される(Api._fetch)。Auth.logout()のredirectUri = origin + pathname)。APP_ADMIN がadmin.htmlではなくテナントスコープのメンバー画面(notification-settings.html)に着地。notification-settings.jsはisAppAdminで弾かず/api/auth/meを呼ぶ。/api/auth/meは設計上「X-Tenant-Id 不要枠」(基本設計書 §721)だが、TenantContextFilterはヘッダが存在すると membership 検証するため、staleX-Tenant-Id:1で 403(admin は tenant1 非所属)。影響
対処案
Auth.logout()でApi.setTenantId(null)(sessionStorage.tenantIdクリア)。ログイン成功直後も/me解決前に一旦クリアする。/api/auth/me・/api/auth/tenants/{id}/select・/api/tenants等)は、ヘッダがあっても membership 検証せず素通りにする(設計 §721 と整合)。または該当 API 呼び出し時はクライアントが X-Tenant-Id を送らない。main()冒頭で APP_ADMIN を検出したらadmin.htmlへ誘導(現状index.htmlのみ実施)。logoutのredirectUriを固定の入口にする案も。補足(レルム設計の確認)
tasks単一。SaaS Admin は別レルムではなく realm ロールAPP_ADMINで区別(基本設計書 §6.2 / ADR-0006)。「別レルムでは?」という違和感の正体は本バグ(セッション/テナントコンテキストの混線)であり、レルム構成自体は単一で設計どおり。関連