ThirdPartyログインの導入と認証フローのリファクタリング

ThirdPartyログインの導入と認証フローのリファクタリング

最近、VizendではThirdPartyログイン機能の導入およびリファクタリングを行いました。ThirdPartyログインは、Google、Apple、Facebook、Keycloakなどの外部認証手段を通じてユーザーを確認し、その結果をVizendの内部ユーザー体系と連携する機能です。ユーザーから見える画面では、よくある「外部アカウントでログイン」ボタンのように見えますが、サーバーの観点では、外部認証の結果を内部アカウント、登録ポリシー、アカウント連携ポリシー、セッションポリシーにどのように結び付けるかが重要です。

今回の作業で重視した点は、外部認証と内部ログインを分離することでした。外部providerは、ユーザーが特定のアカウントの所有者であるという事実を確認できます。しかし、そのユーザーがVizend内部でアクティブな状態か、どの企業に所属しているか、どの権限を持つべきか、サービスへのアクセス条件を満たしているかは、providerには判断できません。この判断は、Vizendのユーザーモデルと認証ポリシーの中で行う必要があります。

したがって、ThirdPartyログインは、外部認証の成功をそのまま内部ログインの成功として扱う機能ではありません。外部認証の結果を標準化し、内部ユーザーとの連携状況を確認したうえで、必要に応じて新規登録または既存アカウントとの連携へつなげる認証補助フローに近いものです。本稿では、VizendにThirdPartyログインを導入・リファクタリングする過程で整理した設計方針、適用プロセス、主な考慮事項を共有します。

導入の背景

VizendのGateは、複数のサービスで共通して使用する認証基盤です。各サービスがGoogleログイン、Appleログイン、Keycloakログインを直接実装することもできますが、そうすると認証ポリシーがサービスごとに分散します。初期段階では迅速に見えるかもしれませんが、providerやサービスが増えるほど運用コストが高くなり、ポリシー変更時の影響範囲を把握することも難しくなります。

例えば、ある企業ではGoogleログインを許可し、別の企業では社内Keycloakのみを許可する必要があるかもしれません。あるサービスではメールアドレスが検証済みのユーザーだけに自動登録を許可し、別のサービスでは外部ログインは許可するものの自動登録は無効にする必要があるかもしれません。このようなポリシーを各サービスのコードに分散すると、同じ基準を繰り返し実装することになり、障害が発生した際の原因も分散します。

もう一つの背景は、外部アカウントと内部アカウントの関係です。ユーザーがGoogle認証に成功したからといって、直ちにVizendの内部ユーザーであるとは限りません。すでに内部アカウントを持つユーザーかもしれませんし、まだ登録していないユーザーかもしれません。外部認証には成功していても、内部では無効化されているユーザーである可能性もあります。そのため、外部認証の結果を内部アカウントに結び付ける別のステップが必要です。

ThirdPartyログイン導入・リファクタリングの基準は、次のように整理できます。

  • providerごとの認証方式の違いを、Gate内部の共通フローに吸収します。

  • 企業ごとに使用するproviderと登録ポリシーを異なるものとして設定できるようにします。

  • 外部アカウント情報は内部ユーザーと直接混在させず、連携情報として管理します。

  • 新規登録と既存アカウントの連携を明確に区別します。

  • 外部認証後も、内部ユーザーの状態、権限、セッションポリシーを維持します。

設計方針

設計の核心は、providerごとの差異と内部認証ポリシーを分離することです。OAuthという名称は同じでも、providerごとの実際の動作は異なります。あるproviderはaccess tokenでユーザー情報を取得し、別のproviderは認証コードをtokenに交換する必要があります。Appleのように、id token自体からユーザー識別情報を確認する場合もあります。

この違いを登録ロジックやアカウント連携ロジックが直接認識すると、providerを一つ追加するたびに既存フロー全体の分岐が増えてしまいます。そこで、providerごとの認証結果をGateが理解できる共通ユーザー情報に変換し、その後のフローではproviderの種類に関係なく同じ基準で処理する構成にしました。

共通ユーザー情報には、外部ユーザーID、メールアドレス、名前、メールアドレスの検証有無、provider識別子などが含まれます。その後の内部ユーザーマッチング、新規登録の可否判断、既存アカウント連携の可否判断は、この共通情報とVizendのポリシーを基準に行います。これにより、providerごとの実装は前段に隔離され、ドメインポリシーを安定して維持できます。

ポリシーはコードに固定せず、企業ごとの設定として管理する方針を選択しました。外部ログイン設定は運用中にも変更される可能性があります。redirect URIが変更されたり、Keycloak realmが分離されたり、特定のproviderを一時的に無効化したりする必要が生じる場合があります。これらの値をコードに含めると、小さな運用変更でもデプロイが必要になります。ポリシーとして分離すれば、企業単位でより柔軟に対応できます。

外部アカウントの連携情報も別途管理します。内部ユーザー情報にprovider IDを直接格納する方法は単純ですが、拡張性に欠けます。一人のユーザーが複数の外部アカウントを連携することもあれば、特定の外部アカウント連携だけを無効化する必要が生じることもあります。そのため、外部providerのユーザーIDと内部ユーザーIDの間の連携を、別の情報として保存する構造がより適しています。

全体フロー

ThirdPartyログインの全体フローは、外部認証、Gateによる確認、内部ユーザーのマッチング、登録または連携という順序で整理できます。まず、ユーザーはクライアント上でGoogle、Apple、Keycloakなどのprovider認証を行います。provider認証が完了すると、クライアントはその結果として受け取ったcredentialをGateに渡します。

image1.png

Gateはまず、対象企業でそのproviderを利用できるかを確認します。providerが無効化されている、またはポリシーが設定されていない場合、外部認証に成功していてもGateでは許可しません。このステップは、運用ポリシーを遵守するための最初の防御線です。

providerの利用が許可されると、Gateはcredentialを確認し、外部ユーザー情報を取得します。このときproviderごとの差異が生じます。Google、Facebook、Apple、Keycloakでは、ユーザー情報の確認方法とレスポンス形式が異なります。しかしGate内部では、これらを共通ユーザー情報に変換して次のステップに渡します。

次にGateは、その外部アカウントがすでに内部ユーザーと連携されているかを確認します。連携済みのユーザーが存在する場合は内部ユーザー情報を返し、その後のサービスアクセスはGateの標準ログインおよびtokenポリシーに従います。連携済みのユーザーが存在しない場合は、すぐに登録するのではなく、次のステップに進むための短期registration tokenを発行します。

registration tokenは、外部認証の結果を安全に引き継ぐための仕組みです。外部認証の直後に、ユーザーがすぐ登録を完了するとは限りません。ユーザー名を決めたり、追加情報を入力したり、既存アカウントに連携するかを選択したりする必要がある場合があります。この間、provider user IDやメールアドレスなどの重要な値をクライアントが直接保持すると、改ざんのリスクが生じます。サーバーが外部認証の結果を一時的に保管し、クライアントには有効期間の短いtokenだけを渡すことで、このリスクを軽減できます。

新規登録を選択すると、Gateはregistration tokenを確認し、登録ポリシーを適用します。対象企業で会員登録が許可されているか、providerベースの登録が有効になっているか、メールアドレスが必要か、検証済みのメールアドレスが必要かなどを確認します。条件を満たすと、内部ユーザーを作成し、外部アカウントの連携情報も同時に保存します。

既存アカウントとの連携を選択した場合は、ユーザーに内部アカウントのusernameとpasswordを再入力してもらいます。外部認証に成功したという事実だけで既存の内部アカウントに連携するのは危険です。共有端末やアカウント切り替えの状況では、誤った外部アカウントが内部アカウントに連携される可能性があります。内部アカウントのパスワードを再確認することで、ユーザーが実際にその内部アカウントの所有者であるかをもう一度確認できます。

Providerごとの差異への対応

ThirdPartyログインで最初に整理すべき部分は、providerごとのレスポンスの違いです。OAuthまたはOIDCという共通標準があっても、実際の連携ではproviderごとに提供される情報と検証方法が異なります。

Googleは、メールアドレスとその検証有無を比較的明確に提供します。この値は、自動登録や自動連携ポリシーにおける重要な判断材料になります。一方、Facebookはメールアドレスを取得できる場合でも、同じ方式で検証有無を信頼することは困難です。このような場合は、保守的に処理する方が安全です。

Appleはid tokenを中心としたフローを持ちます。ユーザー識別子はtokenのsubjectを基準に扱うことができ、メールアドレスもtoken claimとして渡される場合があります。ただし、名前の情報は常に安定して提供されるわけではなく、private relay emailのような特性も考慮する必要があります。そのため、Apple連携を、ユーザーprofile情報を常に完全に取得できるという前提で設計してはなりません。

Keycloakは標準的なOIDCフローに近いものの、設定への依存度が高くなります。realm、client、redirect URI、client authentication methodによって、token交換方式やuserinfoの取得方式が異なる場合があります。特に顧客企業や社内IdPと連携する場合は、endpointとclient認証方式が環境ごとに異なる可能性があるため、ポリシーベースの設定が重要です。

このようにproviderごとの差異は存在しますが、Gate内部のフローでは共通ユーザー情報に変換したうえで処理します。この構造により、providerの追加や変更時の影響範囲を縮小できます。新しいproviderが追加されても、登録ポリシー、アカウント連携ポリシー、内部ユーザーの検索フローを作り直す必要はありません。

新規登録と既存アカウント連携の分離

外部認証に成功していても、内部の連携情報がない場合、そのユーザーが新規ユーザーなのか、既存アカウントを持つユーザーなのかは分かりません。この状態でサーバーが任意に登録を進めると、重複アカウントが作成される可能性があります。逆に、メールアドレスが同じという理由だけで既存アカウントに即時連携すると、誤ったアカウント連携が発生する可能性があります。

image2.png

この問題を軽減するため、外部認証の確認結果と実際の登録または連携を分離します。Gateはまず外部ユーザーを確認し、連携された内部ユーザーが存在しない場合はregistration tokenを発行します。その後、ユーザーが新規登録を選択すれば登録フローへ進み、既存アカウント連携を選択すればアカウント連携フローへ進みます。

この分離は画面フローにも役立ちます。クライアントはGateのレスポンスを確認し、すでに連携済みのユーザーであればログインフローを継続し、未連携のユーザーであれば登録またはアカウント連携画面を表示できます。providerごとに画面ロジックを変える必要が少なくなります。

登録時には企業のポリシーを確認します。外部providerを利用した登録が許可されているか、メールアドレスが必要か、メールアドレスの検証が必要か、管理者の承認が必要かなどを判断します。このポリシーを通過して初めて内部ユーザーが作成され、外部アカウントの連携情報が保存されます。

既存アカウントの連携時には、内部アカウントの所有者確認を行います。外部providerの認証結果だけで既存アカウントに連携するのではなく、内部アカウントの認証情報を再度確認します。このステップは、誤った連携を防ぐ重要な安全装置です。

メールアドレスベースの自動連携

ThirdPartyログインにおけるメールアドレスベースの自動連携は便利ですが、注意が必要な機能です。ユーザー体験だけを考えれば、providerから取得したメールアドレスと内部アカウントのメールアドレスが同じ場合に自動連携するのは自然です。しかし、メールアドレスの文字列が同じであるという事実だけでは、アカウントの所有権が検証されたとはいえません。

そのため、単純なメールアドレスベースの連携と、検証済みメールアドレスに基づく連携を区別する必要があります。providerがメールアドレスの所有権を検証済みであることを明確に示している場合は、自動連携の信頼性が高くなります。逆に、検証有無を確信できないproviderの場合は、自動連携を制限する方が安全です。

また、同じメールアドレスを持つアクティブユーザーが複数存在する場合は、自動的に一人を選択してはなりません。認証システムにおける曖昧な自動判断は、セキュリティ事故につながる可能性があります。この場合はポリシー違反として処理し、ユーザーが明確な手順でアカウントを整理または連携できるよう促す方が適切です。

メールアドレスベースの連携は、利便性とセキュリティのバランスが必要な領域です。VizendのThirdPartyログインでは、providerごとのメールアドレスの検証有無と、内部ユーザーの重複状態を併せて考慮するようにフローを分けました。

Registration Tokenの役割

registration tokenは、外部認証と内部の登録または連携の間をつなぐ短期tokenです。外部providerの認証結果には、provider user ID、email、email verified、nameなどの情報が含まれます。これらの値は登録や連携に必要ですが、クライアントが任意に変更して送信してはならない値です。

registration tokenを使用すると、サーバーが外部認証の結果を保管し、クライアントにはopaque tokenだけを渡すことができます。その後、登録または連携リクエストを受けると、サーバーはtokenを確認して元の外部認証結果を復元します。この方式により、外部認証結果が改ざんされる可能性を低減し、外部認証と内部処理の間に生じる時間差を安全に扱えるようになります。

tokenは短時間だけ有効である必要があり、一度使用したら再利用できないようにする必要があります。ユーザーが登録画面に長くとどまるとtokenが期限切れになることがあり、ブラウザの戻る操作や二重送信によって、すでに使用したtokenが再度送信される場合もあります。このような場合は、外部認証のステップへ再度案内するのが自然です。

運用環境では、tokenの保存方式も重要です。単一インスタンスではメモリベースの保存でも単純に動作しますが、複数インスタンスに拡張された環境では共有ストレージが必要です。tokenを発行したインスタンスと消費するインスタンスが異なる可能性があるためです。そのため、拡張性を考慮すると、Redisのような共有ストレージを基盤として発展させるのが適切です。

適用結果

ThirdPartyログインの導入およびリファクタリング以降のフローは、providerごとの実装とドメインポリシーに分離されました。Google、Facebook、Apple、Keycloakは認証方式が異なりますが、Gate内部では同じフローとして扱えます。providerごとの差異は前段で整理され、その後は内部ユーザーのマッチング、登録ポリシー、アカウント連携ポリシーが共通して適用されます。

運用面でもメリットがあります。企業ごとにproviderを有効化または無効化でき、providerごとのclient設定やredirect URIもポリシーとして管理できます。特定のproviderに問題が発生した場合でも、認証構造全体を変更せず、そのproviderのポリシーだけを調整できます。

セキュリティ面では、外部認証と内部ログインの判定を分離したことが最も重要です。外部providerの認証に成功しても、内部ユーザーの状態とポリシーを通過しなければ、サービスへのアクセスにはつながりません。ユーザーが外部アカウントを正常に保有していても、Vizend内部で非アクティブ状態であれば、アクセスを許可しないことができます。

保守面では、provider追加のコストが削減されます。新しいproviderが追加された場合、そのproviderのcredentialを確認し、共通ユーザー情報に変換する部分を追加すれば済みます。登録ポリシー、連携ポリシー、内部ユーザー検索のフローはそのまま利用できます。認証手段が増える可能性のあるサービスでは、この分離が長期的に重要です。

まとめ

ThirdPartyログインは、単純なOAuth API呼び出しではなく、外部認証の結果を内部ユーザーモデルと連携する認証フローです。ユーザーから見るとボタン1つに見えますが、サーバー内部では外部認証、内部ユーザーの状態、登録ポリシー、アカウント連携、セッションポリシーがすべて連動しています。このフローを明確に分けなければ、実装は速く見えても、運用や拡張で困難が生じます。

VizendのThirdPartyログイン導入およびリファクタリングでは、外部providerの差異を前段で整理し、企業ごとのポリシーは設定として管理し、実際のサービスアクセスの判定は内部ユーザーと認証ポリシーを基準として維持する方針を選択しました。この構造により、providerが増えても認証モデルの一貫性を維持できます。

認証領域では、利便性よりも誤った連携を防ぐことが重要です。メールアドレスが同じという理由だけで自動連携しないこと、検証されていないメールアドレスを信頼しないこと、一度使用したtokenを再利用できないようにすることは、いずれも同じ方向性の判断です。ユーザーにもう一段階の確認を求めることになっても、誤ったアカウント連携を防ぐ方が安全です。

今後は、registration tokenの保存先を運用上の拡張性に合わせて共有ストレージベースに移行し、手動連携の承認や管理者承認などのポリシーを運用画面とさらに緊密に連携させることができます。providerごとの失敗原因や処理時間をモニタリングすれば、障害対応もより迅速になるでしょう。ThirdPartyログインは、外部ログイン手段を増やす機能にとどまらず、さまざまな認証手段を内部ユーザーモデルと一貫して連携するための基盤機能と捉えることができます。

David

Site footer