プラットフォームとして OIDC による ID 連携機能を設計したけど白紙に戻したので記事として供養します
はじめに
ビットキーで bitkey platform を開発しています @otakakot です。
Platform Engineering
いまだにこの言葉を自信を持って説明できないですが、社内の開発者が開発しやすくなる状況を作ってあげるのは Platform Engineering
になると思います。
この前開催された OpenID Summit TOKYO 2024 に参加したら 「 OIDC は簡単だ」 なんてメッセージを受け取りました。
え、結構難しいけどな ...
しかし、歴史的にみると簡単になったのだとか。なるほど。それは納得です。
でも難しいっちゃ難しい。
私はまだ自信を持てていないです。
OIDC の導入を進める上で、もうちょっと開発者が使いやすく設計すれば Platform Engineering
として社内のサービス開発者がもっと本質的な機能開発に集中できるのではないか。
そんな思いを馳せて OIDC による ID 連携機能を設計しました。
しかし、この設計は断念します。
その理由を説明し、供養の意味を込めて本記事にて紹介しようと思います!
ビットキーのざっくりアーキテクチャ
ビットキーはざっくりと赤枠内のようなシステム構成となっています。
bitkey platform (以下: BKP ) を奥に構えエンドユーザーに近いところに各サービス (workhub, homehub) が構築されます。
BKP は 認証(ID) と 認可(権利) を管理するプラットフォームです。
BKPでは認証機能を自前で実装・提供していますが、それぞれのサービスごとにもアカウントを管理するためのセッションを管理していたりします。
そのため BKP が払い出すアクセストークン(リフレッシュトークン)は各サービスが抱える DB へと保存されています。
(User Agent に返さないです。)
一部サービスで OIDC による認証方式を提供していましたが、今後の広がりを考えて BKP として使いやすくなるように再設計しようというのが本機能開発のスタートです。
設計(シーケンス図)
正常系のユースケースしか記載していませんが、シーケンスとして以下のようなものを考えました。
ざっくりと「認可コードフローを2回やる!」です。
大きく 3 つのステップに分かれ、それぞれでプラットフォームとして API を提供する設計としています。
Step 1 OpenID Provider へのログイン
サービス側に連携したい OpenID Provider を選択してもらい SSO ログインへと導きます。
このとき BKP として機能提供する API 定義は以下となります。
<A> GET /oidc
Step 2 ID トークンの発行
認可コードを元に OpenID Provider の ID トークンを取得します。
このとき BKP として機能提供する API 定義は以下となります。
<B> GET /callback
Step 3 BKP アクセス(リフレッシュ)トークンの発行
2度目の認可コードフロー(?) を実施して BKP のアクセス(リフレッシュ)トークンを発行します。
このとき BKP として機能提供する API 定義は以下となります。
<C> POST /oidc
設計できた!実装できた!プロバイダー登録を進めよう!
設計もできて、実装も進めてモックアプリを使って連携の目処もたったので本格的に RP の登録をし始めます。
例えば、Google だと以下のような情報が必要になります。 ※ 必須値ではないです。
ん、プライバシーポリシー?利用規約?
あれ、これってサービスごとに違う??
ということは、サービスごとに OpenID Provider への設定が必要になるのでは … ???
う〜ん、この情報を BKP で管理する … ???
可能ではあるだろうけど、それだとサービスと密になりすぎてない … ???
........ この設計やめよう!!!
反省
検証するだけだからとりあえず動かせればいいやと軽んじた結果です。
検証を始めるタイミングでプライバシーポリシーや利用規約についてしっかりと考えておくべきでした。
また、同時期にパスキーの検証もしていてそのときにも同じような問題を考えていました。
気づけましたね。反省です。
同じような悩みを抱える企業様、ぜひ実装に入る前にご検討ください!
個人的には、実装を通してOIDCに関してたいへん学びがありました。
ほかにも管理を考える必要があるもの
<A> GET /oidc
エンドポイントと <B> GET /callback
エンドポイントにおいてはエラーが発生した場合、リダイレクトURIのクエリパラメータとして Error を載せてクライアントにレスポンスとして伝達する想定でした。しかし、リダイレクト先が確定していない状態でもエラーが発生する可能性があります。
例えば以下のようなパターンです。
- <A>
GET /oidc
エントポイントにおいてprovider
やservice_redirect_uri
が不正な入力だった場合 - <B>
GET /callback
エンドポイントにおいてキャッシュとして保存していたservice_redirect_uri
が取得できなかった場合
このとき、エラーをどうやって伝えてあげるべきかという問題があります。
普遍的なエラー画面を返そうかと設計していましたが、できることならちゃんとサービスごとに適切な画面を返したいです。
他にも、OpenID Provider のアクセストークンやリフレッシュトークンを BKP で保有する設計にはしていません。
ID連携がしたかっただけなので。
ユースケースが増えてID連携先のアカウント情報が必要 & 任意のタイミングで欲しいなどの要望があれば検討する必要がありそうです。
もやりポイント
設計はしたものの、もやもやしているポイントがあります。
2度目の認可コードフローです。
サーバー間認証でお茶を濁した感じです ...
許可されていないサーバーからのアクセスは防げますが、同一のセッションであることを担保できていない気がします。
(PKCE みたいな仕組みを入れれば同一セッションであることを担保できるのかな ... )
おわりに
実際に手を動かして検証するのはとても大事です。早ければ早いほど良いです。
幸いにも、今回はそこまで作り込んでいる状況ではありませんでした。
今後、やっぱりこっちの方が良かったってなるかもしれないですが ...
そして、改めて認証まわりは難しい .. .!!!
認証まわりは難しい上に、理解していないとセキュリティホールを生み出してしまうので慎重に検討しないといけないですね。
でも、やってて楽しい分野です。
認証まわり設計した方々は頭が良いなって常々思います。
(攻撃手法を思いつく方々も頭良いです ... )
これからもっと Deep Dive していきます。
Discussion