こんにちは。FLYWHEELでソフトウェアエンジニアをしてますsaoiです。前回の投稿の投稿に引き続き、FLYWHEELの広告配信プラットフォームにおける、配信管理画面の認証をAuth0へと引っ越した際のお話をしようと思います。
今回は実践編ということで、移行時にどのようなAuth0の機能を利用したか、また移行においてどのような点に留意したかについて書いていきます。
移行方法の検討
自社実装をしていた認証フローをAuth0を用いて置き換えるにあたり、前回説明したようにAPIサーバーのデータベースのユーザーテーブルからAuth0へとユーザー情報を移し替えなければいけません。
Auth0のユーザー移行ページに詳しい説明がありますがAuth0はいくつかのユーザー移行プロセスをサポートしています。
- 既存ユーザーデータベースからの自動マイグレーション
- Auth0 Management APIによるバルク移行
- Auth0 Extensionを用いたJSONファイルベースによるバルク移行
1の既存データベースからのマイグレーションはAuth0を使う理由となり得る強力な機能です。こちらに説明されている通り、移行中の中間状態を仮定しながら、既存ユーザー、新規サインアップユーザーの処理を自動で行います。但し、既存データベースへの接続を行うにはEnterpriseプランの契約が必要です。魅力的な機能でしたが、移行検討時の我々のアクティブユーザー数を考慮するとEnterpriseプランはオーバーフィットだったのでこちらは採用しませんでした。今後プランのアップグレードも検討対象になるでしょう。
また2と3については予め作成しておいた既存ユーザーのリストを一括でインポートする形式で、特にプランなどの制約もなく使用することができますが、移行に時間がかかる場合、移行中に発生した新規ユーザーのサインアップに対応する必要があるので、予期しない中間状態を防ごうと思うと、管理画面機能の一部にダウンタイムを取る必要が出てきます(一時的に新規ユーザサインアップを停止するなどの措置)。さらに手動の作業によるミスを極力減らすという視点からも別の方法の検討をするのが良いという考えに至りました。
というわけで公式にサポートされている移行シナリオから見事に漏れてしまったので、Auth0のManagement APIのリストと睨み合った結果、若干裏技的な次の方法を取ることにしました。
- APIサーバーにある既存の認証ロジックに、Management APIを用いたAuth0への移行機能を実装して自動マイグレーションを行う
一見すると1-3に比べて実装コストが高そうに見えますが、Management APIのSDKが充実しており、Javaで実装されている(APIサーバーの実装はJava)Management APIクライアントとそのサンプルがすぐに見つかったので比較的容易に実装できそうだという見通しが立ちました。
移行計画と実行
前回の投稿で説明したとおり、管理画面はUIを提供するSingle Page Application(SPA)とデータベースから情報を取得・更新するためのバックエンドAPIサーバーで構成されています。Auth0の設定・インテグレーションそのものについてはこちらの公式ドキュメントを参考に進めました。サンプル実装もGitHub上に豊富に公開されています。
Auth0が発行するトークンの形式はJson Web Token (JWT) で、FLYWHEEL自社実装の認証においても同じくJWTを用いたトークンの振り出しと検証をしていたので、組み込みそのものは比較的スムーズに行えました。
しかしSPAとAPIサーバーでデプロイが別系統であることや、リリースのサイクル・タイミングを完全に一致させることは難しく、以下のような中間状態を想定して段階的にデプロイを行う必要がありました。
- SPA: 旧バージョン↔API: 旧バージョン
- SPA: 旧バージョン↔API: 新バージョン
- SPA: 新バージョン↔API: 新バージョン
ステップ0は初期状態です。トークンの発行とユーザーの管理はAPIサーバーが行っています。
ステップ1ではAPIサーバーのバージョンのみを先に上げます。SPAのバージョンを先に上げてしまうとAPIサーバーがTokenの検証に失敗しAPI呼び出しが全て失敗してしまうので、先にAPIサーバーのバージョンを上げる必要があります。
デプロイ後の初期化終了直後に、Management APIのCreate Userを用いたAuth0へのプロセスが走るように構成しておきます。これによって既存ユーザーの一括インポートが行われます。またこれ以降新規にサインアップされるユーザーについては、APIサーバーのサインアップAPI呼び出しがされた際に、Auth0のCreate Userを呼んで新規ユーザーはAuth0側に作成されるように実装します。
旧バージョンのSPAから、ログイン(トークン発行)のAPI呼び出しがあった際にも、APIサーバーはAuthentication APIのLoginを発行することによって、ブラウザ経由でAuth0にアクセスしトークンを取得するのと同じ様にAuth0からアクセストークンを取得することができます。
このステップによって既存・新規のユーザーは全てAuth0に作成され、ログインの際に振り出されるアクセストークンも全てAuth0が発行したものに置き換えることができます。
ステップ2ではSPAにあるログインフォーム(ユーザー名・パスワードを入力するフォーム)をAuth0のUniversal Loginに置き換えます。ステップ1のままでも稼働はし続けますが、ソーシャルログインや多要素認証などの対応を簡単に行うためにもUniversal Loginへ移行してしまうほうが後々の利益が大きいと判断しました。また、自社でログインフォームを実装・メンテナンス(例えばパスワードのポリシーチェックやフォームのセキュリティ対策など)するコストも抑えることができます。
認証をAuth0に移行したことによるメリット
上で説明した過程を経て、無事FLYWHEELの広告プラットフォーム管理画面の認証をIDaaS (Identity as a Service) であるAuth0で置き換えることができました。これによってユーザーの認証情報を自社管理する必要がなくなり、Auth0が認証に関する各種セキュリティ対策を最新に保ってくれます(SDKのバージョン追随などは自分でする必要はあります)。
また当初一部のパートナー様に限って公開していた管理画面をさらに多くのパートナー様に開放するにあたって、認証に用いるメールアドレス確認とパスワードのセルフリセット機能が必要であったのですが、この2つに関してもAuth0に移行することによって機能自体の実装コストは0で追加することができました。認証プロセスにおいてこの2つは当たり前にあるべき機能なのですが、自前で実装しようと考えるとなかなかに骨が折れる作業です。
RBACの導入
管理画面用のAPIサーバーでは広告主やそこに紐づく広告などの個々のリソースに対するユーザーごとの権限情報を管理し、アクセス認可を行っています。しかし障害対応時などに全てのオブジェクトに対して自動的に閲覧・編集権限があるような最上位の管理者権限を実装したいとなったときに、既存の権限情報を管理するテーブルを更新してそこに最上位管理者の概念を導入するとなると、なかなかの設計と実装コストがかかるという話になりました。
ここで利用した機能がRBAC(Role Based Access Control)です。やや不正確ですが簡単に言うと「APIサービスに対して○○の操作権限を持つ」という権限情報(permission)を複数まとめてロール(role)という権限情報のセットを作り、Auth0上にあるユーザーにこのロールを割り当てる事ができます。
今回は最上位管理者という新しい権限の概念をpermissionとして定義し、スーパー管理者というroleをAuth0に定義することによって、Auth0が発行するJWTに付加情報としてこの権限情報を載せました。APIサーバー側ではこの権限情報をもとに挙動を変えるという処理を実装することによって、必要な機能がDBのスキーマの変更なしに実装することができました。こちらの話についてはここで書くと長くなるので、別途詳しい内容をいつか書こうかと思います。
まとめ
今回は前回のPart1-導入編に引き続き、実際にどのようなプロセスとAuth0の機能を用いて認証を移行したかについて書かせて頂きました。今回紹介した移行プロセスは若干Auth0が公式に推奨しているものから外れていはいますが、移行そのものは(多少のトラブルは無論ありましたが)大変スムーズで、Auth0の開発者向けの柔軟性を実感しました。認証の移行を検討している方の参考になれば幸いです。
おわりに
ここからはごく個人的なつぶやきですが、今回Tech Blogに投稿をするというのは12年+の私のエンジニア人生のなかで初めての経験で(まったくもって恥ずかしい限りですが)FLYWHEELに来なければおそらく一生機会がなかったかもしれない事でした。しかし弊社エンジニアyukuのこの投稿にあるように、日々アウトプットを意識して仕事をして、外に発信してく作業というのは殊スタートアップでは重要なことですし、今回やってみて、エンジニア個人のキャリアとしても間違いなくプラスに働くというのが実感です。何事もやってみなければ始まらない、というのはそのとおりでした。
というわけでFLYWHEELでは今まで経験はないけれど、新しいことに挑戦して楽しんでみたい、という仲間も募集しています!
Happy Christmas!