Table of Contents
この文字起こしは、2024 年 12 月 11 日に開催された Authlete Customer and Partner Meetup 2024 ののプレゼンテーションのひとつです。フリー株式会社 IAM 基盤開発部の寺原歩氏に、Authlete を利用した freee 認可基盤の刷新や移行ノウハウについてお話しいただきました。
フリー株式会社(以降、freee)の寺原と申します。今日は freee における OAuth/OpenID Connect の活用と Authlete への移行についてお話します。
freee の IAM 基盤開発部でエンジニアをやっています、寺原と申します。freee にはニックネーム文化がありまして、気軽に「てらら」と呼んでいただければと思います。金融系 SIer や SES を経て 2021 年に freee に入社し、IAM 基盤開発部で ID 連携の開発に従事しています。
Authlete 人材として freee が 3 社目です。私も先月まで知らなかったんですけど、開発言語やフレームワークのような技術スタックのかたちで、Authlete を使ったことがある・使うスキルがある方を「Authlete 人材」と呼んでいるということなので、私も今日から名乗らせていただきたいと思います。freee では、エンジニアもやりつつ、兼務の DevRel で「freee Tech Night」という配信イベントだったり「freee 技術の日」というテックカンファレンスの運営などを行っていました。
弊社は「スモールビジネスを、世界の主役に。」をミッションに掲げており、統合型経営プラットフォームを開発・提供しています。
freee 会計をはじめ、人事労務や販売管理、電子契約、経費精算と様々なバックオフィスのツールとして、これさえあれば統合型経営ができる「統合型経営プラットフォーム」を目指しています。
Web での提供に加え、パブリック API による拡張性もあります。さらにサードパーティ開発者向けに、アプリを作って公開していただく「freee アプリストア」というプロダクトもあり、今公開していただいているアプリケーションが 179 件ほどあります。これらを使って様々なサービスと連携し、お客様自身の業務をより効率化していただくというコンセプトです。
freee のモバイルアプリも提供しています。「いつでも・どこでも利用できる機能の提供」ということで、freee 人事労務モバイルを使うと位置情報で自動打刻できるような便利な機能もあります。弊社では OAuth 認可サーバーの提供する機能として、ファーストパーティのモバイルアプリ(OAuth クライアント)の管理をモバイル向けにやっています。
さらに freee グループジョインプロダクトに freee アカウントログインを提供しています。これは OpenID プロバイダーとしての機能です。
「freee 販売」という元々あったプロダクトから「freee サイン」というグループジョインプロダクトに発注書を送って、freee サインで署名をして取引完了というような、ユーザーにとっての統合体験の一貫性を、OpenID Connect を使って確保しています。
さまざまなプロダクトがあり、モバイルもあり、OpenID Connectもあり、色々やっています。急成長する freee プラットフォームにおける認可基盤の課題を、いっぱい書いてまいりました。
アプリストアで色々連携していますし、非公開のアプリもかなり多く存在しています。たくさんのアプリごとの認可管理や API アクセス制御の複雑化など、これらの要件の拡大が課題となっています。freee モバイルアプリもどんどん増えていきますし、モバイルアプリならではの認証要件があります。グループジョインプロダクトの統合についても、OAuth クライアントではなく、OpenID Connect のリライングパーティとして管理しないといけないということで、権限管理が複雑になってきました。
技術的課題として挙げられるのが、RFC への準拠の完全性です。RFCはあくまでプラクティスです。10年前ぐらいのプラクティスが今現在においてもベストかというと、そうではありません。たとえば Resource Owner Password Credentials フローとかいろいろありますけど、準拠の完全性の課題が必ず現れます。
また、OAuth では拡張仕様がどんどん出てきます。新規格への対応の困難さと、あとはセキュリティ更新の負担です。運用面の課題としては、プロダクトが続々と増えることによるコストの増大や、さまざまな監視要件があります。スケーラビリティにも課題があります。
ということで、認可基盤を刷新することになりました。
まず決めなけばいけないところが、基盤移行の要件として、ミドルウェアだったりとかフルスクラッチとか、どう実装するかっていう話です。
現行に関してはフルスクラッチによる実装を行っていました。また、元々は認証認可基盤という1つのサービスだったのですが、そこから認証部分は切り離されています。よって現行基盤の名称は旧「認証認可基盤」なんですけど、実は認可機能しかない、認可基盤になっています。
既存の認証基盤を切り離したので、その認証基盤の ID 体系や ID 体験との親和性が必須要件となります。加えて、freee 独自の「事業所アカウント」との親和性です。これは B2B SaaS ではありがちですが、一般ユーザーのアカウントの 1 個上に、所属関係を表す「事業所」という概念があります。「事業所のアカウントに所属している」という関係性を表さないといけない、っていうところとの親和性です。
パターンとしては、「フルスクラッチ」「Authlete」「IDaaS A」となっています。前述した 2 つの「既存の認証機能との親和性」「事業所アカウントとの親和性」が必須要件です。それらを満たすのは「フルスクラッチ」と「Authlete」で、ほぼ決定というところです。
freee はこれからも企業として成長していきます。そのためには、事業領域の拡大に耐えうる基盤を作らないといけません。そうすると、OAuth に関しても、拡張機能の開発を行わないといけないんですけれども、それをやりきる人間だったりとか、システムだったりとかを確保するのは、かなり大変ということが経験上わかっています。
よって、Authlete の採用により開発が非常に軽くなることを見込みました。あとは、Authlete の採用により、設計・開発のサポートを受けられるので、今後の開発においてこれらの実装において困ることはないと思いました。
インフラの月額費用に関しては、「フルスクラッチに自社サーバー」の方が安いんですけども、その差額は、フルスクラッチのために OAuth に熟達したエンジニアを1人雇うよりも、全然小さいです。それらの総合的なコストを踏まえて、Authlete を採用した方が良いと判断しました。
ということで、freee の新認可基盤は、Authlete と共に歩んでいきます。
そして、移行プロジェクトが始まりました。実際の移行方式について説明します。
freee のバックエンドは Ruby on Rails と Go による gRPC で構成されています。バックエンドのマイクロサービスは Go で作っています。今回の認可基盤も Go による実装です。
OAuth クライアントがアクセストークンを投げて、freee プロダクトのパブリック API がそれを受け取ります。freee プロダクトでは「認証認可ライブラリ」という自社製のライブラリを使っています。そのライブラリから旧認可基盤に対して問い合わせを行って、「認証認可ライブラリ」が認可判定して、パブリック API がリソースを返す、という流れになっています。
旧環境のシーケンスとほとんど同じですが、「認証認可ライブラリ」が旧認可基盤に問い合わせを行うと同時に、新認可基盤に対して問い合わせを行います。それらの結果を踏まえて認可判定し、リソースを返す流れになっています。
こういったシーケンスを踏まえて、移行の方式として、「ストラングラーフィグパターン」を使っています。全てのプロダクトが使っている「認証認可ライブラリ」を「ストラングラーファサード」にしています。
移行戦略として、まず二重書き込みを行い、旧基盤に例えばアクセストークンがどんどん発行されるんですけど、それと同時に新認可基盤にも書き込みます。これにより、Authlete のアクセストークンの状態と旧基盤のアクセストークンの状態を揃えます。データを揃えたら、次の段階として、二重読み込みを行って、データ差分やインターフェースの差分を検知します。
これを使うことによって、今弊社には 40 以上のプロダクトがありますが、各プロダクトに 1 つ 1 つ切り替えてねとお願いしても絶対切り替わらないところ、プロダクトの開発者は意識せずに新基盤に移行可能、という流れになっています。
移行パターンについて、もう少し詳細な話をしていきます。今回は2つのフェーズを用意しました。
まず、弊社の freee アプリストアから作成されるデータである、OAuth クライアントの移行を行いました。OAuth クライアントの管理に関しては、freee 自身、独自のドメインを持つ機能がかなり多かったので、ドメインの再モデリングをしながら移行を進める必要がありました。レガシーコードへの対応になったので、技術的負債の返済を主軸にフェーズ 1 の移行を進めました。
アクセストークンは RFC によってシーケンスなどがしっかり定義されているので、基本的には会社独自のドメインを持つことは無さそう、という仮説を立てました。ただ一方でアクセス量やデータ量が段違いに多いので、パフォーマンスチューニングが必要なフェーズとなります。
注意点というか、あるあるですけど、OAuth クライアントから飛んでくるアクセストークンは必ずしもアクティブなものではありません。たとえばリボーク済みやリフレッシュ済みのアクセストークンがけっこうあります。そういうものを Authlete に対して問い合わせても絶対 invalid になるのはわかっています。なので、クライアント開発者との折衝などにより、そういうリクエストを減らす努力は必要かと思います。
弊社では書込みを二重書き込みで行っています。主系・副系で順次に書き込んでいくので、主系が旧基盤であれば、新基盤はセカンダリーとして動きます。それが切り替わったときに、新基盤がプライマリーとして動きます。
流れとしては、Secondary Write で二重書き込みを行って、新基盤にデータを順次流していきます。2 つ目に Secondary Read で二重読み込みすることによって、新基盤のデータとインターフェースが、現行と新規で一致することを保証します。そして、Primary Read / Primary Write で、主系と副系を入れ替えて、新基盤のみで動作可能であることを保証していきます。これで、ダウンタイム無しで、順次切り替えていくことができます。
フェーズ 1 は OAuth クライアント情報の順次移行、差分修正、主副の切り替えを行います。こちらは技術的負債の返済がベースとなっています。
フェーズ 2 はアクセストークン情報の順次移行、差分修正、順次切り替えです。こちらはパフォーマンスチューニングのフェーズとなっています。
実際にストラングラーフィグパターンを使ってみて、Authlete との相性の良さをひしひしと感じています。
Authlete の特性として、RFC 準拠の質と速さがすごいです。ストラングラーフィグパターンではどうしても新旧基盤の機能やインターフェースの差分が出ます。一般的にはその場合、主系に合わせて副系を改修する必要があります。ですが、Authlete を使った副系の新基盤の方が RFC に準拠していて、こちらの方が機能面で優秀なケースが多く見られました。よって、副系に合わせて freee の仕様を更新することにより、今まで見えていなかったリスクを回避できました。
実際の改善例として、OpenID Connectのリライングパーティがトークンリフレッシュする際に、ID トークンを再発行していました。これはおそらく、リライングパーティ側でセッション更新のような用途に使うのかなと想定しています。ただし弊社のリライングパーティに関してはいずれもこの機能を利用しておらず、無駄だったので、削除しました。
新基盤のインターフェースを揃えるために、Authlete API の仕様を読みながら開発を行いました。Authlete の API は基本的に RFC に準拠していますので、Authlete の API 仕様と合わせて各種 RFC を開発チーム内で一緒に読み合わせて進めることが習慣付きました。これは、開発をやっていると最終的にそうなります。この結果、設計の品質が向上しました。
あとは小話ですけど、Authlete の川崎さんの解説記事をみなさんも読まれたことがあると思います。それをチーム内に共有すると「:taka_god:」という絵文字リアクションがつくようになりました。Taka ゴッド、ありがとうございます。
移行に際しては、Client ID Alias、Client Attributes、/auth/token/create API、Access Token Properties など、いろいろな Authlete のオプションを使いました。これらの機能によって、旧基盤で発行したクライアントIDやアクセストークンをそのまま新基盤でも使えるようになりました。移行中に、インターフェイスを揃えるために旧基盤を通して、そのまま二重読み込み時も、特に考慮することなく同じものが読み込めるようになっています。
また、Client AttributesやAccess Token Propertiesを用いて、freee 独自のドメイン情報の判定のための値を設定しました。これにより、機能の現新一致が可能になりました。
移行後に、これ使いたいなと思ったのは、冪等性リフレッシュトークンです。おそらく、OAuthクライアントやリライングパーティが増えてくると、トークンリフレッシュを並列で実行するようなユースケースも出てきます。そういったところを解決できる機能です。
新認可基盤上に実装したキャッシュ機能について、その実装方法を説明します。
Authlete では、アクセストークンをハッシュ化して保持しています。生のアクセストークンが Authlete からそのまま返ってくることはありません。
freee では、アクセストークンやリフレッシトークンを指定する一般的なトークンリボケーション以外に、freee アプリストアというプロダクトの中で、特定ユーザーがログインして、特定のアプリケーションのアクセストークンの連携を解除したい、といったかたちで、リソースオーナーや OAuth クライアントを指定して、一括でリボケーションを行う機能があります。
そうなってくると、各仕様がバッティングしてしまいまして、キャッシュ要件に響いてきます。
まず、トークンイントロペクションによる Authlete へのリクエスト負荷を下げることが第一です。ただ、一括でトークンリボケーションされた場合でも、ちゃんとアクセストークンのキャッシュを無効化する必要がありました。
それらの要件を満たすために、キャッシュのパターンを3つ用意しました。
こちらが、トークンイントロスペクションにおけるキャッシュのシーケンス図です。
最初にお話しした通り、例えば自社のモバイルのクライアントとか、自社のグループジョインのリライングパーティーだったりとか、そういうパターンに応じてクライアントの属性値を管理する必要が出てきます。
最初のうちは identifier 単位で管理してもいいんですけど、かなり大変です。なので、それをパターンにマッピングして、そのパターンに使う識別子を、なんと Authlete では Client Attributes の中に設定できます。そちらに保持した情報をいろんな判定に使えます。
ということでブログを書いたので、ぜひはてブにブックマークをお願いします。
freee は統合型経営プラットフォームを提供しており、これをさらに追求していきます。「業務が、最も効率的に」「組織が、ストレスフリーに」「経営が、自由に」と、経営の 3 つの分断を解決する統合フローを定義しています。
こちらを意識し、スモールビジネスに最高の体験を最速で提供するために、基盤のエンジニアリングにおいては進化可能なアーキテクチャーを目指しています。
認可基盤に関しては、B2B SaaSの認可のベストプラクティスを目指していきたいと思います。
freee ならではなのかもしれませんが、「リソースオーナー」が結構複雑化しがちです。最初にお話した通り、freee の「事業所」みたいなドメインとか、例えば事業所の情報を取得する際のスコープの設定として、オーナーは一体誰なのか、わざわざ社長に認可を取らないといけないのかといった、正しいリソースオーナーから認可を取るのは難易度が高いかと思います。
そういったお話などを、認可機能に特化してる Authlete さんとともに、B2B SaaS の認可のプラクティスを追求していきたいと思います。
「スモールビジネスを、世界の主役に。」ということで、私の発表を終わります。ご清聴ありがとうございました。