メインコンテンツまでスキップ

OIDC Session Management

このドキュメントの目的

OIDC Session Managementの仕様を理解し、IdPがなぜ特殊なセッション管理を必要とするかを学ぶことが目標です。

所要時間

約40分

学べること

  • OIDC Session Managementの目的
  • OPSession と ClientSession の関係
  • 複数のログアウト方式
  • セッション検索の要件

なぜ通常のセッション管理では不十分か

一般的なWebアプリのセッション

【通常のセッション管理】

Browser ──── SESSION=abc123 ────→ Webアプリ


セッションデータ
├── user: alice
├── cart: [...]
└── preferences

・1ブラウザ = 1セッション
・セッションIDでのみアクセス
・シンプルで十分

IdP(Identity Provider)のセッション

【IdPのセッション管理】

┌───────────────────────────────┐
│ IdP │
│ │
Browser ────────────│──→ OPSession │
│ │ sub: alice │
│ │ authTime: 10:00 │
│ │ │
│ ├── ClientSession(App A)│
│ │ sid: sid-001 │
│ │ │
│ ├── ClientSession(App B)│
│ │ sid: sid-002 │
│ │ │
│ └── ClientSession(App C)│
│ sid: sid-003 │
│ │
└───────────────────────────────┘

・1ブラウザ = 1 OPSession
・1 OPSession = N ClientSession
・階層的な構造

違い:

  • 複数のアプリケーション(RP)のセッションを管理
  • ユーザーがどのアプリにログインしているか追跡
  • ログアウト時に全アプリに通知が必要

OIDC Session Managementとは

仕様群

OIDC Session Managementは複数の仕様で構成されています。

仕様目的
Session Managementiframeでセッション状態を監視
RP-Initiated LogoutRPからのログアウト開始
Front-Channel Logoutブラウザ経由でログアウト通知
Back-Channel Logoutサーバー間でログアウト通知

基本概念

【OPSession と ClientSession】

┌─────────────────────────────────────────────────────────────────┐
│ OPSession │
│ │
│ ブラウザとIdP(OP)間のセッション │
│ │
│ 属性: │
│ ├── id: セッションID │
│ ├── sub: ユーザー識別子(誰がログインしているか) │
│ ├── authTime: 認証時刻(いつログインしたか) │
│ ├── acr: 認証強度(どの強度で認証したか) │
│ └── amr: 認証方式(どの方法で認証したか) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ClientSession│ │ClientSession│ │ClientSession│ │
│ │ │ │ │ │ │ │
│ │ App A │ │ App B │ │ App C │ │
│ │ sid: xxx │ │ sid: yyy │ │ sid: zzz │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ClientSession: OPSessionとRP間のセッション │
│ ├── sid: ID Tokenに含まれるセッションID │
│ ├── clientId: どのアプリか │
│ └── scopes: どの権限が許可されたか │
│ │
└─────────────────────────────────────────────────────────────────┘

シングルサインオン(SSO)

SSOの仕組み

【SSOの流れ】

1. ユーザーがApp Aにアクセス
┌────────────────────────────────────────────────────────────┐
│ Browser → App A: アクセス │
│ App A → Browser: IdPへリダイレクト │
│ Browser → IdP: 認可リクエスト │
│ │
│ IdP: ログインフォーム表示 │
│ User: ID/パスワード入力 │
│ │
│ IdP: │
│ ① OPSession作成 │
│ ② ClientSession(App A)作成 │
│ ③ IDP_IDENTITYクッキー発行 │
│ │
│ Browser ← IdP: 認可コード + Cookie │
│ Browser → App A: 認可コード │
│ App A: ID Token取得(sid: xxx) │
└────────────────────────────────────────────────────────────┘

2. ユーザーがApp Bにアクセス
┌────────────────────────────────────────────────────────────┐
│ Browser → App B: アクセス │
│ App B → Browser: IdPへリダイレクト │
│ Browser → IdP: 認可リクエスト + IDP_IDENTITY Cookie │
│ │
│ IdP: │
│ ① IDP_IDENTITYからOPSession取得 │
│ ② 有効なセッションあり → ログイン画面スキップ │
│ ③ ClientSession(App B)作成 │
│ │
│ Browser ← IdP: 認可コード │
│ App B: ID Token取得(sid: yyy) │
└────────────────────────────────────────────────────────────┘

→ 2回目以降はログイン画面なしでアクセス可能

prompt パラメータ

認可リクエストで prompt パラメータを使ってセッションの扱いを制御できます。

動作
noneセッションがあれば自動認可、なければエラー
login既存セッションがあっても再認証を要求
consent既存セッションがあっても同意画面を表示
select_accountアカウント選択画面を表示
【prompt=none の流れ】

Browser → IdP: GET /authorize?...&prompt=none

IdP:
├── OPSession あり → 自動的に認可、ClientSession作成

└── OPSession なし → エラー: login_required

ポイント:

  • prompt=none はサイレント認証(ユーザー操作なし)
  • 有効なOPSessionが必要
  • SPAのセッション確認によく使われる

ログアウト

RP-Initiated Logout

RPからログアウトを開始する方式

【RP-Initiated Logout の流れ】

1. ユーザーがApp Aでログアウトボタンをクリック

2. App AがIdPにリダイレクト
GET /logout?
id_token_hint=<ID Token>&
post_logout_redirect_uri=https://app-a.com/logout-callback&
state=abc123

3. IdPがセッションを終了
├── OPSession削除
├── 全ClientSession削除
└── IDP_IDENTITY Cookie削除

4. IdPが指定URLにリダイレクト
→ https://app-a.com/logout-callback?state=abc123

ポイント:

  • id_token_hint でどのセッションかを特定
  • post_logout_redirect_uri で戻り先を指定
  • ブラウザリダイレクトベース

Back-Channel Logout

サーバー間通信でログアウトを通知する方式

【Back-Channel Logout の流れ】

1. ユーザーがログアウト(IdPまたは任意のRP)

2. IdPが全ClientSessionを検索
「このユーザーのClientSessionをすべて取得」

※ここでユーザーIDでの検索が必要
※HttpSessionでは不可能 → 直接Redisアクセスが必要

3. IdPが各RPにLogout Tokenを送信
┌────────────────────────────────────────────────────────────┐
│ POST https://app-a.com/backchannel-logout │
│ Content-Type: application/x-www-form-urlencoded │
│ │
│ logout_token=eyJhbGciOiJSUzI1NiIsInR5cCI6Imp3dCJ9... │
│ │
│ Logout Token (JWT): │
│ { │
│ "iss": "https://idp.example.com", │
│ "sub": "user-123", │
│ "aud": "app-a", │
│ "iat": 1704067200, │
│ "sid": "xxx", ← どのClientSessionか特定 │
│ "events": { │
│ "http://schemas.openid.net/event/backchannel-logout": {}│
│ } │
│ } │
└────────────────────────────────────────────────────────────┘

4. 各RPがローカルセッションを終了
App A: sidがxxxのセッションを削除
App B: sidがyyyのセッションを削除
App C: sidがzzzのセッションを削除

ポイント:

  • ブラウザを経由しない(サーバー間通信)
  • ユーザーがオフラインでも通知可能
  • 信頼性が高い
  • ユーザーIDでのセッション検索が必須

Front-Channel Logout

ブラウザ経由でログアウトを通知する方式

【Front-Channel Logout の流れ】

1. ユーザーがIdPでログアウト

2. IdPがログアウト確認ページを表示
┌────────────────────────────────────────────────────────────┐
│ <html> │
│ <body> │
│ <p>ログアウト中...</p> │
│ │
│ <!-- 各RPのlogout URLをiframeで読み込み --> │
│ <iframe src="https://app-a.com/logout?sid=xxx"/> │
│ <iframe src="https://app-b.com/logout?sid=yyy"/> │
│ <iframe src="https://app-c.com/logout?sid=zzz"/> │
│ </body> │
│ </html> │
└────────────────────────────────────────────────────────────┘

3. 各RPがiframe内でログアウト処理
・ローカルセッション削除
・Cookie削除

ポイント:

  • ブラウザ経由(iframe)
  • Cookie削除などブラウザ側の処理が可能
  • 3rdパーティCookieブロックの影響を受ける
  • すべてのiframeの読み込み完了を待つ必要がある

ログアウト方式の比較

方式通信経路信頼性制約
RP-Initiatedブラウザ → IdPブラウザ操作必要
Back-ChannelIdP → RP(サーバー間)最高RPがエンドポイント必要
Front-ChannelIdP → RP(iframe経由)3rdパーティCookie制限

セッション検索の要件

なぜ複合検索が必要か

【ログアウト時に必要な検索】

1. RP-Initiated Logout
id_token_hint からsubとsidを取得
→ sidでClientSessionを検索
→ ClientSessionからOPSessionを取得

2. Back-Channel Logout
OPSession終了時
→ sub(ユーザーID)で全ClientSessionを検索
→ 各RPにLogout Token送信

3. Front-Channel Logout
OPSession終了時
→ opSessionIdで全ClientSessionを検索
→ 各RPのlogout URLをiframeで表示

必要なインデックス

検索パターン用途
opSessionId → OPSessionセッションデータ取得
sub → 全OPSessionユーザーの全セッション取得
sid → ClientSession特定のClientSession取得
opSessionId → 全ClientSessionログアウト通知
clientId + sub → ClientSession特定アプリのセッション確認
【Redisインデックス設計例】

# メインデータ
op_session:{tenantId}:{opSessionId}
client_session:{tenantId}:{sid}

# インデックス
idx:tenant:{tenantId}:sub:{sub} → [opSessionId, ...]
idx:tenant:{tenantId}:op_session:{opSessionId} → [sid, ...]
idx:tenant:{tenantId}:client:{clientId}:sub:{sub} → [sid, ...]

Session Management iframe

セッション状態の監視

RPがIdPのセッション状態をリアルタイムで監視する仕組み

【Session Management iframe】

┌─────────────────────────────────────────────────────────────────┐
│ RP (App A) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ RPページ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ OP iframe (hidden) │ │ │
│ │ │ src: https://idp.example.com/check_session │ │ │
│ │ │ │ │ │
│ │ │ 定期的にpostMessage: │ │ │
│ │ │ "client_id session_state" │ │ │
│ │ │ │ │ │
│ │ │ → Cookieからセッション状態を計算 │ │ │
│ │ │ → "unchanged" / "changed" / "error" を返す │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ JavaScriptがpostMessageを監視: │ │
│ │ ・unchanged → 何もしない │ │
│ │ ・changed → prompt=noneで再認証 or ログアウト処理 │ │
│ │ ・error → エラー処理 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

2つのCookieの役割

CookieHttpOnly用途
IDP_IDENTITYYesサーバー側でセッション識別
IDP_SESSIONNoiframe(JavaScript)でセッション状態確認
【なぜ2つ必要か】

IDP_IDENTITY (HttpOnly=Yes)
├── JavaScriptからアクセス不可
├── サーバー側でのみ使用
└── セキュリティのため必須

IDP_SESSION (HttpOnly=No)
├── JavaScriptからアクセス可能
├── Session Management iframeで使用
├── セッション状態のハッシュ値
└── 実際のセッションIDは含まない(安全)

まとめ

重要ポイント

項目説明
OPSessionブラウザとIdP間のセッション(SSO用)
ClientSessionOPSessionと各RP間のセッション
sidClientSessionの識別子(ID Tokenに含まれる)
Back-Channel Logoutサーバー間通信、ユーザーID検索が必要
複合検索sub, sid, clientIdでの検索が必要

IdPでの検索要件

【必要な検索パターン】

┌──────────────────────────────────────────────┐
│ セッションID → セッションデータ │ ← 通常のセッション
├──────────────────────────────────────────────┤
│ ユーザーID → 全セッション │ ← Back-Channel Logout
│ OPSession → 全ClientSession │ ← ログアウト通知
│ sid → ClientSession │ ← RP-Initiated Logout
│ clientId + sub → ClientSession │ ← 再認可確認
└──────────────────────────────────────────────┘

→ HttpSession(セッションIDのみ)では不十分
→ 直接Redisアクセスでインデックス必要

次のステップ

次に読むべきドキュメント:


関連仕様