はじめに
こんにちは。コミュニケーションIT事業部の石田です。
普段はソフトウェアエンジニアとしてiPLAssというローコード開発プラットフォームの開発を担当しています。本記事は 電通国際情報サービス Advent Calendar 2023 の12/14の投稿です。
iPLAssとは、コーディング量を最小限に抑えた迅速なアプリケーション開発を実現するプラットフォームです。ブラウザ操作によるノンプログラミングでのデータ定義やその他各種定義(認証ポリシーや画面表示設定など)を行うことでアプリケーションを構築していくことが可能です。
また、データ管理画面の自動生成、認証/認可、ID連携、帳票出力、通知、カスタムロジック組込、WebAPI開発といったアプリケーションの開発に必要となる機能をデフォルトで提供しています。
iPLAssの機能の一つに、Web上に公開されるコンシューマ向けサイトを会員制サイト化し、iPLAss上で会員管理を行うことを可能とするWAM機能があります。本記事では、WAM機能のエンハンスとして、CDNエッジ上のJavaScriptランタイムで動作するポータブル(=Node.jsやWorkersなどあらゆるJavaScriptランタイムで動作する)な認証・認可プロキシを実装した話を紹介します。
WAM機能とは
まずは、冒頭で述べたiPLAssのWAM機能について簡単に説明します。
WAMは Web Account Manager
の略であり、その名の通り、Web上に公開される任意のコンシューマ向けサイトを会員制サイト化し、iPLAss上で会員管理を行うことを可能とする機能です。
※ 記事公開時点において、WAM機能はエンタープライズ版限定で提供している機能になります。
WAM機能を使用することで、コンシューマ向けサイト側には手を加えずとも、会員制サイト化が可能になります。また、既存の会員制サイトについて、会員管理機能のみをiPLAssに置き換えて、その他は既存サイト(コンテンツ)を再利用するといったことも可能です。
WAM機能はあくまでiPLAssの機能の一つであり、iPLAssが具備するその他の豊富な機能と組み合わせることにより、あらゆるニーズ・ユースケースに対応できます。例えば、ヘッドレスCMSのようなコンテンツ管理システムが一般的に具備している機能と同等なデータ管理画面(コンテンツ管理)、データの有効期間ベースのバージョン管理(コンテンツのバージョン管理・予約投稿)、ワークフロー(承認ワークフロー)、監査ログ記録、WebAPI開発といった機能も提供しており、iPLAss上でコンテンツ管理も併せて行ってしまうといったことも可能です。
WAM機能は、以下2つのモジュールから構成されます。
- WAMモジュール
- WAMプラグインモジュール
- HTTPサーバー上でプロキシとして動作し、サイトコンテンツへのアクセスをフックしてコンテンツ閲覧制御を行うモジュールです。WAMモジュールとHTTP連携し、閲覧権限に応じたアクセス制御、未ログインユーザーのログイン画面へのリダイレクト、認証成功時のアクセストークン受け渡しなどを行います。
これまでは Apache HTTP Server
、IIS
、JavaEE
上で動作するWAMプラグインモジュールを提供してきましたが、今回新たにCDNエッジ上のJavaScriptランタイムで動作する前提で実装されたServerless(JavaScript)版の提供を開始しました。
CDNの前段でエッジプロキシとして動作させて認証・認可チェックを行うことで、CDNによるコンテンツキャッシュを最大限活用することが可能になっています。
ここからは、新たに開発したServerless(JavaScript)版の全体構成や実装概要について説明します。
全体構成図
以下が、全体構成のイメージ図になります。
技術構成
実装概要
認証・認可プロキシのコアロジックはライブラリ化し、プライベートnpmリポジトリからnpmパッケージとして利用者に配布しています。併せて、各CDNエッジランタイムとのアダプターとなるnpmプロジェクトのひな形をいくつか提供するという形をとっています。「ポータブル」を意識し、コアロジックの実装では、特定のランタイムに依存しないAPIのみを使用しています(Fetch APIなど)。
ひな形プロジェクトでは、配布するnpmパッケージ内のAPI(コアロジック)を利用する形で、各CDNエッジランタイムのハンドラー関数をデフォルトで実装しています。ハンドラー関数の他にも、認証・認可プロキシのアプリケーション設定およびカスタマイズのベースをあらかじめ実装しており、利用者がプログラマブルに設定を行ったり、カスタムロジックを追加したりできるようにしています。
例えば、アプリケーション設定については、ハードコードする以外の選択肢として、環境変数を埋め込んだり、Lambda@Edgeの場合にはAWS AppConfigやParameter Storeといったサービスを利用するなどの選択肢を取ることが可能です。カスタマイズについては、コアロジック自体をカスタマイズすることや、フックして処理を追加することが可能であり、オリジナルのHTTPリクエストを改変する(リクエストヘッダーの追加、クエリパラメータの追加など)といったカスタマイズが可能です。
また、各CDNエッジランタイムのビルド・デプロイ用のnpmスクリプトなどもデフォルトで実装しており、利用者がアプリケーション設定とカスタマイズ実装にフォーカスできるようにしています。
プライベートnpmリポジトリ
プライベートなnpmリポジトリにはSonatype Nexus RepositoryのOSS版を利用しています。構築したNexus RepositoryはプライベートなMavenリポジトリとしての役割も兼ねており、iPLAssエンタープライズ版のJavaモジュールの配布にも利用しています。
Nexus RepositoryはOSS版でMavenリポジトリとnpmリポジトリの両方に対応しており、とても重宝しています。
CDNエッジ(ランタイム)
現在、npmプロジェクトのひな形を配布しているCDNエッジランタイムサービスは以下の3つです。
- Cloudflare Workers
- Lambda@Edge
- Netlify Edge Functions
それぞれのサービスの詳細説明はここでは割愛しますが、Workers、Node.js、DenoとあらゆるJavaScriptランタイムで実際に動作することが確認できています。今後もCDNエッジランタイムのトレンドを追いながら対応環境を拡大していきたいと考えています。
Webフレームワーク
Webフレームワークとして、unjs/h3を採用しています。
h3は、Nuxt 3のサーバーエンジンの基となっているWebフレームワークです。
UnJSと呼ばれるJavaScriptライブラリ、ツール、ユーティリティ群を開発するプロジェクト(Nuxt開発チームを主導していたPooya Parsa氏が開発リーダーを務める)によって提供されているライブラリの一つです。
ミニマルかつポータブルを特徴とした高速なWebフレームワークであり、「CDNエッジ上のあらゆるJavaScriptランタイムで動作させる」という今回の構想にマッチすると判断しました。また、Nuxt 3で利用されているライブラリであるということもあり、信頼性が高いという点も採用理由として挙げられます。個人的にVue.js/Nuxt.jsが好きで、Nuxt 3のキャッチアップをしているときにUnJSに出会いました(Vue.js/Nuxt.js好きというバイアスがかかっていることも否定できません)。
h3では、v1.8.0リリース時に Towards the Edge of the Web!
を掲げており、Node.js環境でHTTPサーバーとして動作させる以外に、直接h3を使用するための2つのアダプタ(fetch互換のシグネチャを持つWebAdapter、それ以外のプレーンな入出力オブジェクトを使用するPlainAdapter)が提供されました。これにより、Cloudflare WorkersやLambda@EdgeといったCDNエッジランタイムでh3を直接使用することが容易になりました(従来は、unjs/nitroを使用するか、unjs/unenvと組み合わせたカスタムコードを実装するしかありませんでした)。今回実装した認証・認可プロキシにおいても、各CDNエッジランタイムのハンドラー関数の実装でこれらのアダプタを使用しています。
v1.8.0のリリースに関する詳細は、以下の公式ブログ記事をご参照ください。
https://unjs.io/blog/2023-08-15-h3-towards-the-edge-of-the-web
Pooya Parsa氏のGitHubリポジトリでいくつかのランタイムプラットフォームを対象としたサンプルが公開されています。興味があれば参照してみてください。
https://github.com/pi0/h3-on-edge
一方、今回実装した認証・認可プロキシの挙動としては、
- 認証・認可プロキシから直接レスポンスを返したいケース(未認証ユーザーのログイン画面へのリダイレクトなど)
- 後続のCDNにコンテンツ取得処理を委譲したいケース(コンテンツ閲覧が許可されるリクエストなど)
という2パターンが存在しており、特にLambda@EdgeやNetlify Edge Functionsでは、Webフレームワークの挙動(常にレスポンスを返す挙動)とマッチしていない点もあります。それらのサービスでは、ハンドラー関数の戻り値として、①のケースの場合はレスポンスオブジェクトを、②のケースの場合はリクエストオブジェクトをリターンすることが期待されており、常にレスポンスオブジェクトをリターンする訳ではないためです。
Webフレームワーク自体を利用しないことも検討しましたが、検証の末、以下の理由から最終的にh3の採用を決めました。
- h3には、上述のような挙動に有用な
onAfterResponse
、onError
などのグローバルフックが存在すること - h3の導入による処理のオーバーヘッドが少なく十分高速であったこと
- コアロジック実装の共通化・簡易化ができると考えたこと(コアロジックでは、リクエストやレスポンスを取り扱うイベントオブジェクトとしてh3のAPIのみを使用しており、ランタイム差異を意識しないで済む)
- 利用者側でのカスタムロジック実装時にh3が提供する便利なユーティリティを利用可能とすることで利便性向上に繋がると考えたこと
h3が提供するユーティリティの一覧は以下のページで説明されています。
https://www.jsdocs.io/package/h3#package-functions
その他の主要ライブラリ
その他の主要な依存ライブラリを以下に列挙します。
- ランタイム
- nanoid(IDジェネレータ)
- unjs/destr(高速なJSONパース)
- unjs/ufo(URL操作ユーティリティ)
- 開発用途
- TypeScript(静的型付けを加えたJavaScriptのスーパーセット)
- eslint(静的解析)
- vitest(単体テスト)
- msw(テスト時のAPIモック)
- unbuild(ビルドツール)
終わりに
本記事では、iPLAssの新機能としてCDNエッジで動作するポータブルな認証・認可プロキシを実装した話について紹介しました。昨今盛り上がりを見せているCDNエッジですが、一つのユースケース・実例としてどなたかの参考になれば幸いです。
現在、iPLAssでは積極的な機能強化・製品展開に取り組んでおり、同じチームで働いてくれる仲間を大募集しています。ご興味を持っていただいた方は是非採用ページをご覧ください。
私たちは一緒に働いてくれる仲間を募集しています!
製品・プラットフォーム開発エンジニア執筆:@ishida_yuma、レビュー:@nakamura.toshihiro
(Shodoで執筆されました)