Bill One Engineering Unit 共通認証基盤チームの樋口です。
Bill Oneでは昨年までAuth0を認証基盤として利用してきましたが、認証基盤を内製化することでコストを大幅に削減しました。
この認証基盤は、昨年12月に無事リリースされ、Bill Oneの認証を支えています。
今回は認証基盤の内製化に至った経緯と設計、移行プロセスについて紹介します。
Bill Oneについて
Bill Oneは、BtoB SaaSとして提供しているインボイス管理サービスです。
本サービスは 請求書受領から、月次決算を加速する
をタグラインに掲げ、あらゆる請求書をオンラインで受け取り・請求書業務を効率化することで、企業経営における意思決定のスピード向上を目指しています。
Bill Oneはユーザー数に基づく課金をしておらず、利用企業が必要なだけユーザーを作成していただくことが可能です。 また、Bill Oneは請求書の代理受領を促進するために、利用企業が普段取引をしている企業(請求書を送付する企業)にもアカウント(送付用のアカウント)を作成しています。
Bill Oneは急速に成長しており、直近一年で有料契約件数は倍に、MAU(Monthly Active User)は倍以上に伸びています。
なお、以上の図のMAUはAuth0の利用があったユーザーのMAUでありデータの欠損もあるため、参考程度にご覧ください。
認証基盤に関する課題
Bill OneはこれまでAuth0を認証基盤として利用してきました。
Auth0は非常に機能が充実しており多くの採用実績がありますが、他のIDaaSと比較して高い価格設定となっています。
前述の通り、Bill Oneではユーザー数課金を行っていないことに加え、経理業務という都合上、月に一度しかBill Oneを利用しないアカウントも多く存在します。
そのためAuth0の価格体系であるMAU課金とは相性がよくありません。MAU単価が高い場合、認証に関わるアクションの数に比べて割に合わないコストを支払うことになってしまうともいえるでしょう。 1
プロダクトの成長に伴うユーザー数の増加によって、価格体系によるBill Oneとの相性問題が認証基盤のコストという形で顕在化してきました。
そこで、Auth0リプレースによるコスト最適化の検討が始まりました。
解決方法の検討
認証基盤のコストの問題を解決するため、どのような選択肢を取るかを検討しました。
IDaaSを移行する、もしくは内製するにあたって次のようなグラデーションが考えられます。これまでの要件を満たせるか、コストの削減が見込めるかという観点で考えていく必要がありそうです。
- Auth0以外のIDaaSへ移行
- Auth0以外のIDaaSへ移行 + 自前開発
- KeycloakのようなOSSを利用
- KeycloakのようなOSSを利用 + 自前開発
- 完全自前開発
もともと、認証の機能をできるだけマネージドサービスに寄せることで開発工数は抑えつつも、可用性やセキュリティを担保したいという意図でAuth0を利用していました。 Auth0に依存しない認証基盤を実現するためにkeycloakのようなOSSを利用したり、自前で開発したりすると、機微な情報の管理やセキュリティ対策に対して全面的に責務を負う必要があります。
必要な要件を満たしつつ、引き続きマネージドサービスを利用できると嬉しいので、Auth0以外のIDaaSのコスト・機能について調査しました。
IDaaS(Identity as a Service)について
認証基盤をできるだけマネージドサービスに寄せるため、Auth0以外のIDaaSへ移行できるか検討しました。
IDaaSの選択肢としては以下があげられました。
- Amazon Cognito
- Google Cloud Identity Platform
- Azure Active Directory B2C
まずIDaaSの移行が現実的なのかコストについてみていきます。
AWS Cognito | Google Cloud Identity Platform | AzureAD B2C(Premium P1) | |||
Monthly Active Users (MAU) | Price per MAU | Monthly Active Users (MAU) | Price per MAU | Monthly Active Users (MAU) | Price per MAU ($) |
最初の 50,000 | $0 | 最初の 49,999 | $0 | 最初の 50,000 | $0 |
次の 50,000 | $0.0055 | 50,000 - 99,999 | $0.0055 | 50,000 以上 | $0.00325 |
次の 900,000 | $0.0046 | 100,000 - 999,999 | $0.0046 | ||
次の 9,000,000 | $0.00325 | 1,000,000 - 9,999,999 | $0.0032 | ||
10,000,000 以上 | $0.0025 | 10,000,000 以上 | $0.0025 |
Amazon Cognito Pricing
Google Cloud Identity Platform pricing
Azure Active Directory B2C pricing
上記は2024年7月時点での東京リージョンの価格になります。
表から3つとも課金体系はどれもMAUベースで、概ね同じ価格帯系であることがわかります。
Bill OneではAuth0のEnterpriseプランを利用しており、Enterpriseプランの価格は公表されていません。 そのため、例えば公開されている 価格 - Auth0 のB2B Professionalプランの価格を見ると7500MAUで$1800/monthとなっており1MAUあたり$0.24となっています。
BIll Oneが契約しているプランやMAUの規模が違うため参考の数字になりますが、Auth0に比べて他社のIDaaSは大幅に低コストであることがわかります。
実際にこの段階でIDaaSを移行するだけで大幅なコスト削減になることを確認しています。
IDaaSの移行によってコスト削減になることがわかったため、そのほかの観点で採用するIDaaSを絞っていきました。
Bill OneではクラウドプラットフォームとしてGoogle Cloudを利用しています。 Google Cloudに慣れているエンジニアも多いことからIdentity Platformが有力な選択肢として上がりました。しかし、次の理由からIdentity Platform・AzureAD B2Cの採用を見送り、Amazon Cognitoを採用することにしました。
Amazon Cognito
- TOTP(Time-based One-Time Password)によるMFA(Multi-Factor Authentication)に対応している
- 他と比較し採用事例が多くインターネット上のノウハウも期待できる
Google Cloud Identity Platform
- フロントエンドからの呼び出しで完結するタイプのIDaaSであり、不足している機能をサーバーサイドで補うことが難しい
- TOTPによるMFAに対応していない
- 採用事例が少ない
AzureAD B2C
- Bill OneでAzureになじみのあるエンジニアが少ない
*上記は検討時の評価です。現在では、Identity PlatformでもTOTPによるMFAに対応しているなどの変化があります。
Amazon Cognitoを採用することが決まりましたが、Cognito単体ではログイン画面のカスタマイズやアカウント管理の機能が足りません。
そこで次のような観点から認証基盤の一部を自前開発し内製化することとしました。
- IDaaSを移行すること自体で大幅なコスト削減が見込める
- 部分的に自前開発することで自社特有の要件を実現することが可能になる
設計とシステム構成について
内製化することとなった認証基盤を構築する上での設計とシステム構成について紹介します。
認証基盤の設計
上述の通り、Cognito単体では機能が足りないため、不足している機能を自前で実装する必要があります。
Cognitoを利用する方法には、APIを直接利用する方法とHosted UI(Cognitoが用意しているUI)を利用する方法があります。
Hosted UIはほとんどカスタマイズできないため、CognitoのAPIを直接利用する形でOIDC(OpenID Connect)のOP(OpenID Provider)としてふるまうアプリケーションを開発することにしました。
最終的に認証基盤は、次の3つで構成されています。
ユーザー向けサービス(OP)
- MPA(Multi Page Application)として実装されている
- OIDCのOPとして振る舞う
- エンドユーザーが実際に利用する
管理API
- Connectサーバーとして実装されている
- アカウントのCRUD処理など、RP(Relying Party)のアカウント管理のために必要なAPIが実装されている
- Bill Oneのアプリケーション・管理画面から呼び出される
管理画面
- SPA(Single Page Application)として実装されている
- RPの設定やアカウント情報の管理のために利用する
システム構成
システム構成は次の図のようになっています。Fargateを中心としたシンプルな構成になっていますが、次のような特徴があります。
- エンドユーザーの認証情報の管理だけでなく、管理画面・管理APIの認証でもCognitoを活用し、開発工数を削減
- Aurora Serverless v2やElastiCache Serverlessなどマネージドサービスを積極的に採用し運用工数を削減
アカウントの移行について
認証基盤の移行にあたって、当然アカウント情報も新基盤へ移行が必要になります。
メールアドレス・パスワードでのログインを利用しているユーザーの移行
Auth0はサポートチケットから依頼することでパスワードハッシュを含むユーザー情報のエクスポートが可能 2 ですが、エクスポート作業の具体的な実行日時を保証してもらえません。
また、そもそもAmazon Cognitoはパスワードハッシュの一括インポートをサポートしていない 3 ため他の方法を採る必要がありました。
全ユーザーにパスワードを再度設定してもらう前提で新基盤に移行してしまうという手段もありますが、できればユーザーに手間のかからない方法にしたいという思いがありました。
そこでAmazon Cognitoに用意されているユーザー移行のLambdaトリガー 4 を利用しました。Amazon Cognitoには、 認証等の処理タイミングでLambdaを挟み込むことで処理の挙動を変更する機能が存在します。 ユーザー移行のLambdaトリガーは、ログイン試行もしくはパスワードリセットのタイミングでCognitoのUser Pool5に該当ユーザーが存在しなかった場合にLambdaを呼び出せます。 このLambda関数の中で、次のような処理を行うことで旧基盤から新基盤へアカウント情報を移行できます。
ログイン試行時
ユーザー名・パスワード等の情報を引数として、Lambdaトリガーが呼び出されます。
渡されたログイン情報を利用して旧基盤(Auth0)へResource Owner Password Credentials(ROPC) Flow6でログイン試行します。成功した場合、Lambda関数で所定の値を返すことでCognitoのUser Poolにアカウント情報が登録されます。
この際、ログインに成功したパスワードがCognitoに登録されるため、ユーザーは次回以降も従来のパスワードでログインできます。
パスワードリセット時
ユーザー名等の情報を引数として、Lambdaトリガーが呼び出されます。
この情報を利用して旧基盤(Auth0)でユーザーの存在確認を行い成功した場合、Lambda関数で所定の値を返すことでCognitoのUser Poolにアカウント情報が登録されます。次回以降はパスワードリセット処理で再設定したパスワードを使ってログインすることになります。
基盤移行後の初回ログイン時もしくはパスワードリセットのタイミングでアカウント情報がリアルタイムに新基盤へ移行されるため、ダウンタイムを発生させずに基盤を移行できる点がこの方法の特徴です。 ただし、いくつかの懸念点もあり、以下で4つ紹介します。
- パスワードリセットを開始してアカウント情報が新基盤に移行してしまうと、パスワード未設定のアカウントが新基盤に作成される。この場合、パスワードを思い出したとしても必ずパスワードの再設定を完了させる必要がある。
- ログイン時にSRP(Secure Remote Password)プロトコルが利用できない。これは、ユーザー移行トリガーでのROPC Flowに平文パスワードが必要になるため。本来はSRPを利用することで、平文パスワードをネットワーク上に送信せず認証できる。7
- ログインやパスワードリセット時に入力されたメールアドレスのアカウントがCognitoのUser Poolに存在しなかった場合、同期的にLambdaが呼び出され旧基盤への問い合わせが発生する。そのため、レスポンスが少々重くなる。
- 新基盤と旧基盤を並行で稼働する必要があるため、並行稼働中は旧基盤の運用コストを完全にはなくせない。
Bill Oneとしては1〜3点目は許容しました。しかし、4点目に関しては基盤移行後に一度もログインしていないユーザーの情報は一向に新基盤へ移行されず、何もしなければ並行稼働を止められないという問題があります。並行稼働を終了させるため、期限を設けて一定期間以降ログインしなかったユーザーは一括でインポートし、パスワードは改めて設定してもらうなどの対応が必要そうです。
Bill Oneでも近日中に未移行のユーザーの情報を一括でインポートし、並行稼働を終了する予定です。
SSO(Single Sign-On)の移行
さて、Bill OneはBtoBのサービスであるため、SSOは認証基盤にとって切っても切り離せない要素です。 従来はAuth0のEnterprise Connectionsの機能を利用することでOIDC・SAMLでのSSOを実現していました。 これに相当する機能を新基盤に用意して移行する必要があります。
コスト削減のため、2024年のAuth0契約更新までに新基盤をリリースする必要がありました。完全にAuth0の利用を止めるためには通常のログインに加えてSSOについても新基盤へ移行する必要があります。しかし、SSOを新基盤へ移行するためには莫大な数のテナントにSSOの再設定を行ってもらう必要があり、契約更新までにSSOの実装から移行まで行うことは残り時間的に非現実的でした。
割合としてはSSOを使っていないユーザーが大半であったため、引き続きAuth0を利用したSSO機能を残したとしても大幅なコスト削減が見込めると判断し、SSO機能の内製化は先送りにしました。
この際、SSOでログインする場合は新基盤からAuth0へ認証処理を委譲し3回のID連携することで、顧客にSSOの再設定してもらうことなく新基盤へ移行しています。
さて、上図のように新基盤とAuth0のID連携を実現するためにもOIDC等の実装が必要です。
CognitoにもEnterprise ConnectionsのようにSSOを実現する機能8が存在するため、それを利用してAuth0とのID連携や今後のSSOを実現する方法も検討していました。
しかし、Cognitoを利用する場合Auth0へSSO先を明示するためのパラメータ9を付与できない上、SSOを利用するユーザーは $0.015(per MAU)と通常の数倍の単価のコストが発生します。
そのため、新基盤のOPから直接Auth0へOIDCによるID連携を開始することで、Cognitoの利用を最小化しAuth0を利用したSSOを実現しコストメリットも得ています。SSOを利用するユーザーのアカウント情報に関してはパスワードハッシュ等の機微な情報を管理する必要がないため、あえてCognitoを利用する必要はないと判断しています。こうして顧客へSSOの再設定をしてもらうことに加えて、新基盤へのSAMLクライアントとしての機能を実装することも先送りしました。
振り返りと今後
新基盤は、昨年の12月に無事リリースされました。その結果、リリース後3週間でMAUの約86%のユーザーが新基盤に移行し、認証基盤のコストはAuth0の契約更新で想定されるコストと比べて大幅に削減できました。
しかし、新基盤において反省すべき点や今後の課題とすべき点もあります。
ドメイン変更による問い合わせの増加
基盤の移行に伴って、ログイン画面のドメインが変更になりました。従来は bill-one.com
のサブドメインを利用していましたが、今後当社が提供するプロダクト共通の基盤となることを見越し、 sansan.com
のサブドメインを利用しています。
その結果、営業DXサービス「Sansan」とBill Oneのログイン画面のネイキッドドメインが同じになりました。これにより、両サービスにアカウントがありパスワードマネージャーを使っている場合、Bill Oneのログイン画面にSansanのログイン情報がサジェストされてしまうという問題が発生しました。
サジェストされたSansanのアカウントでログインしようとした場合、パスワードマネージャーに保存している情報を使っているのにも関わらずログインに失敗してしまいます。この影響で一時的にログイン失敗に関する問い合わせが増加してしまいました。
そのため、ログイン失敗時のメッセージにSansanではなくBill Oneに設定しているパスワードの入力を促すメッセージを追加しました。
内製化によって体験の改善がスムーズに
Auth0でLockもしくはUniversal Loginを利用している場合、Enterprise ConnectionsにSSO対象とするメールアドレスのドメインを指定しておくことで、ユーザーがどのコネクション10を利用してログインするのかを指定できます。例えば、ユーザーがログイン画面で user@example.com
というメールアドレスを入力した場合、 example.com
が指定されているコネクションを利用してSSOすることになります。
この仕組みを利用する場合、次のような課題がありました。
- 特定のドメインをEnterprise Connectionsに指定した場合、そのドメインのメールアドレスを利用しているユーザーは必ずSSOすることになってしまう。そのため、特定のメールアドレスのみSSOを利用しないでログインするといったことが難しい。
- あるドメインとひもづけられるコネクションはひとつのみ。そのため、ドメインをグループ会社内で共有しているがユーザーが所属している子会社によってIdPが異なるといったケースに対応できない。
上記は、Bill Oneでも改善要望のあった課題でした。Auth0を利用したままでも、これらの課題に対処することは可能でしたが、内製化によってより柔軟に対応できるようになりました。例えば、メールアドレスごとにログイン方法を設定可能にするなど、独自の仕様を組み込めます。今後も当社のPremiseである「セキュリティと利便性を両立させる」を推進していくため、改善を進めます。
Bill One以外のプロダクトへの展開
現在はBill Oneのみが新基盤を利用しております。 当社の他のプロダクトへの展開は、新基盤の機能の充実度や他のプロダクトが利用している認証基盤のプライシングとの相性を考慮して判断します。
最後に
Bill OneはAuth0の利用をやめ、認証基盤を内製化する選択肢を取りました。 しかし、これまでAuth0を利用してきたことで認証周りの開発運用工数を大幅に削減しプロダクトの本質的な部分にフォーカスできたことは事実です。内製化にあたって、Auth0では当たり前にあった機能がCognitoになく自前開発の必要な部分がいくつもありました。改めてAuth0が非常によくできたサービスであったことを痛感しました。 Auth0の機能は非常に充実していたので、コストの問題さえなければ利用継続できたと思います。他のIDaaSと比べても充実した機能と実績を鑑みると、ユーザー課金のサービスやMAUあたりの収益が高いビジネスモデルであれば十分採用する価値があるサービスでしょう。
共通認証基盤では、今後も認証基盤の機能強化・改善を行っていくとともにBill One以外のプロダクトへの展開や認証以外の基盤整備も検討しています。
Bill One、ひいてはSansanのマルチプロダクトを支えていく基盤の開発に興味のある方の応募をお待ちしております。
選考評価なしで現場のエンジニアのリアルな声が聞けるカジュアル面談もあるので、ご興味ありましたらぜひ面談だけでもお越しいただけたら幸いです。
*米国およびその他の管轄区域において、Cloudflare、Cloudflareのロゴ、Cloudflare Workersは、Cloudflare, Inc.の商標および/または登録商標です。
- IDaaSにおけるMAUはログイン操作等を行うことによって算出されるため、アプリケーション側のセッションが長ければ認証基盤におけるMAUは下げられる。アプリケーション操作のたびにセッションの有効期限が延長されるような仕組みの場合、MAUによるコストは抑えられるかもしれない。しかし、Bill Oneでは定期的に明示的なログインが必要になるようセッションの延長は行っていない。↩
- https://auth0.com/docs/troubleshoot/customer-support/manage-subscriptions/export-data#user-passwords↩
- https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-using-import-tool.html↩
- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html↩
- User Poolは、Amazon Cognitoでカウント情報を保持・認証認可を担うリソースです。 https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html↩
- ROPCの利用は推奨されていませんが、ユーザー移行トリガーの仕組みの中でAuth0へログイン試行をするためにはROPCの利用が必要と判断しました。 https://datatracker.ietf.org/doc/html/rfc6749#section-10.7↩
- 通信自体はHTTPSで行うため、SPRを使わなかったからといって平文パスワードがネットワーク上に露出するわけではない。↩
- https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-federation.html↩
-
Auth0でログインを開始する際、認可リクエストに
connection
のパラメータを追加し利用するEnterprise Connectionを明示することでSSOを開始できる。↩ - Auth0でユーザーが利用する認証方法ごとに作成されるリソースのこと。コネクションの種類には、SSOで利用するEnterprise Connectionsの他にメールアドレス/パスワードでのログインに利用するDatabse Connections等がある。Enterprise Connectionsの場合、SSOを利用する顧客のIdPごとにコネクションを作成しておく必要がある。↩