こんにちは。エンジニアのokuboです。このブログではSTORES アカウントのsid導入およびsidを利用したBack-Channel logoutの実装について、実際の開発現場での課題や仕様、実装例、得られた知見を紹介します。
モチベーション
STORESは複数の独立したサービスで構成されています。事業者様は、同一のSTORES アカウントでこれらのサービスにログインし、サービス間を遷移しながら日々の業務を行っています。 STORES アカウントによる認証機能は、STORESが自社開発しているID基盤システムによって提供しています。
STORES アカウントでは、サービス間を移動する際にログインセッションの不整合が発生することがあり、ユーザー体験に課題がありました。例えば、あるサービスでログインしているアカウントと、他サービスでログインしているアカウントが不整合し、再ログインを求めるケースがありました。
この課題を解決するため、sidによるログインセッションの一元管理を導入しました。
用語
この記事ではOIDCでの用例に則り、STORES アカウントのID基盤システムや、STORES アカウントを使って認証を行うサービスを、以下のように呼称します。
- STORES ID基盤を IdP(Identity Provider) と呼称
- IdPを使って認証を行う個別のサービス(ネットショップ作成サービスや予約サービス)を RP(Relying Party) と呼称
sidとは?
sidは、IdPにおけるログインセッションを識別するID です。IdPのログインセッションのIDを、RP側のログインセッションに紐づけることで、IdPを親としたログインセッションの一元管理や、特定セッションの無効化が可能になります。
- sidは openid-connect-backchannel-1_0 にて定義されています
- より具体的には LogoutToken 及び Back-Channel Logout Request に記載があります
- sidは認証時にIdPが発行し、idTokenに sid claimとして登録します
- IdPをログアウトした際に、IdPからRPへ無効化されたsidをRPに通知することで、該当セッションを削除できます
- sidは公開可能情報として扱います。よってクエリパラメータ等に含めることも可能です
- sidを使って認証を通すことがないので公開可能として良い、という整理にしました
- sidはログアウト連携のためだけに利用するし、ログアウト連携におけるlogout tokenの送信元の正当性は他の方法で担保されており、公開しても十分安全です
- 開発効率、観測性を高めるために公開可能とします
- sidを使って認証を通すことがないので公開可能として良い、という整理にしました
- STORES独自の仕様としてsidはUUID形式としています
IdPのログインセッションのライフサイクル
IdPにログインし、IdPからの明示的なログアウトまたはRPによるログアウト(RP initiated logout)が実行されるまで、IdPのログインセッションは有効です。
- ユーザがIdPにログインすると新しいログインセッションが発行され、sidを発行します
- IdPのログインセッションがすでに存在する場合でも、ユーザーが認証情報を再度入力してログイン(prompt=login)すると、新しいログインセッションが発行され、sidを再発行します
- IdPの既存のログインセッションを利用して認証情報の再入力を省略した場合や、prompt=noneによるサイレントログインでは、ログインセッションは新たに発行されません。sidは変更されず、既存のIdPのログインセッションに発行しているsidを引き継ぎます
- IdPからの明示的なログアウトまたはRPによるログアウト(RP initiated logout)が実行されると、IdPはログアウトされたsidをRPに通知します
RPのログインセッションのライフサイクル
RPのログインセッションは、IdPを用いて認証しidTokenが取得できたことをもって発行します。
- 前提として、各RPは、それぞれのフロントエンドとバックエンド間で独自のログインセッションを持っています
- STORESでは、IdPのログインセッションが破棄されたタイミングで、各RPのログインセッションも破棄する運用としています
- これはOIDCの仕様上必須ではありませんが、システムのシンプルさや開発効率を考慮してこのように設計しています
- ユーザがログアウトした場合、IdPは、ログアウトされたセッションに紐づく sid をRPに通知します。RPは通知された sid に紐づくログインセッションを破棄します
- この運用は OpenID Connect Back-Channel Logout 1.0 に準拠しています
sidの発行とBack-Channel Logoutの具体的な実装例
IdP側
IdPは、ログインセッションごとにsidを発行します。IdPはsidをidTokenのsid
claimでRPに伝えます。
idTokenの例
{ "sub": "user-xxxx", "email": "user@example.com", "sid": "xxxxxxxx", "exp": 1234567890 // ... }
RP側
- 認証時にidTokenからsidを抽出し、RP側のログインセッション情報に紐づけて保存します
- RPは、IdPからsidの無効化通知を受けた際、該当sidに紐づくログインセッションを破棄します
sid取得部分の実装イメージ(Ruby)
omniauthを利用している場合は、デコードや検証が済んだ状態のidTokenに簡単にアクセスできます(Auth-Hash-Schema)。
# 認証時にidTokenからsidを取得 sid = auth.dig('extra', 'raw_info', 'sid').to_s # sidとsessionのマッピングを保存 SidSessionMapping.save!(sid: sid, session_id: session.id)
※RPのログインセッションの仕組みによりますが、単一のsidに対して、同一RPの複数のログインセッションが紐づく可能性がある点を考慮する必要があります。例えば、RP側のログインセッションのIDが記録されたcookieを削除した場合に、IdPのprompt=noneなどでサイレントログインを実行すると、同一sidで再度RPのログインセッションが結ばれます。
まとめ
sid導入によって、ログインセッションをSTORESのサービス間で一貫して扱えるようになりました。特に以下のメリットが得られました。
- セッション単位のトラッキングや監査がしやすくなった
- IdPのログインセッションを親として扱うことで、サービス間を移動する際にログインセッションが一貫するようになった
STORES ID基盤へのsid導入は、セッション管理の柔軟性・堅牢性を大きく向上させるものでした。
今後も、より安全で使いやすい認証基盤を目指して改善を続けていきます。