サーバ関数の暗号化
@lazarv/react-serverはデフォルトですべてのサーバー関数識別子をAES-256-GCMを使用して暗号化します。これにより、レンダリング時に明示的に公開されていないサーバ関数をクライアントが発見したり呼び出したりすることを防止します。
暗号化を行わない場合、サーバー関数識別子は平文の文字列であり、ソースファイルのパスや関数名(例: src/actions#deleteUser)を露呈します。これにより、クライアントは任意のリクエストを作成し、アプリケーション内のあらゆるサーバ関数を呼び出すことが可能になります。これは、そのユーザーに対してレンダリングされたことのない関数であっても同様です。暗号化により、レンダリング時にサーバーから返されたサーバ関数のみが呼び出し可能となります。
サーバ関数トークンは2つのタイプがあります。
インライントークンは、サーバ関数参照が初めて使用された際に生成されます。暗号化されたトークンは関数インスタンスにキャッシュされ、Reactの内部フォーム状態マッチング(例:useActionState)が正しく動作するようにします。インラインサーバ関数およびクライアントコンポーネントにプロップとして渡されるサーバ関数は、RSCストリームに埋め込まれたこれらのトークンを使用します。
静的トークンは、ビルド時(または開発時のVite変換時)にサーバ関数モジュール(最上位に"use server"を含むファイル)に対して生成されます。これらのトークンはクライアント用JavaScriptバンドルに埋め込まれ、クライアントコンポーネントがサーバ関数を直接インポートする際に使用されます。
両方のタイプは同じ鍵で暗号化され、サーバ関数が呼び出された際にサーバー上で透過的に復号化されます。
暗号化は設定不要でそのまま動作します。
- 開発環境では、開発サーバーの起動時に一時キーが自動的に生成されます。
- 本番環境ビルドでは、ビルド中にランダムな32バイトのシークレットが生成され、ビルド成果物として保存されます。本番サーバーは起動時にこのキーを読み込みます。
これはサーバ関数の暗号化が常に有効であることを意味します。独自の秘密鍵を提供したい場合や鍵のローテーションを有効にしたい場合にのみ設定が必要です。
環境変数またはランタイム設定を使用して独自の暗号化シークレットを提供できます。これは、デプロイを跨いで決定論的なキーが必要な場合や、複数のサーバーインスタンスを実行する場合に有用です。
シークレットは次の優先順位で解決されます。
REACT_SERVER_FUNCTIONS_SECRET環境変数REACT_SERVER_FUNCTIONS_SECRET_FILE環境変数(キーファイルへのパス)- ランタイム構成内の
serverFunctions.secret - ランタイム構成内の
serverFunctions.secretFile(キーファイルへのパス) - ビルド成果物 (本番ビルド時に自動生成)
- 一時的なランダムキー (開発時のフォールバック)
REACT_SERVER_FUNCTIONS_SECRET=my-secret-key pnpm start
またはキーファイルを指定します。
REACT_SERVER_FUNCTIONS_SECRET_FILE=./keys/action-secret.pem pnpm start
react-server.config.mjsexport default {
serverFunctions: {
secret: "my-secret-key",
},
};
またはキーファイルを指定します。
react-server.config.mjsexport default {
serverFunctions: {
secretFile: "./keys/action-secret.pem",
},
};
環境変数と設定値は、常にビルド成果物よりも優先されます。これにより、アプリケーションを再構築せずにシークレットをローテーションできます。
暗号化シークレットをローテーションすると、古いキーで暗号化されたトークンを含むページを開いたままのクライアントはエラーが発生します。これを回避するためにサーバーが復号時にフォールバックとして、サーバーが試行できる以前のシークレットを提供できます。
react-server.config.mjsexport default {
serverFunctions: {
secret: "new-secret-key",
previousSecrets: ["old-secret-key"],
},
};
以前のシークレット用のキーファイルも使用できます。
react-server.config.mjsexport default {
serverFunctions: {
secret: "new-secret-key",
previousSecretFiles: ["./keys/old-secret.pem"],
},
};
previousSecretsとpreviousSecretFilesの両方が配列を受け付けます。これらを組み合わせることで、複数の以前のキーを同時にサポートできます。
ローテーションのワークフローは以下の通りです。
- 現在のシークレットを
previousSecretsに追加する secretの新しい値を設定する- デプロイ — 既存のトークンはすべてフォールバック経由で引き続き機能する
- すべてのクライアントが更新された後(例:デプロイメントウィンドウ終了後)、
previousSecretsから古いシークレットを削除する
サーバ関数の呼び出しが復号に失敗した場合(例:トークンが改ざんされた、キーがローテーションされたが以前のキーが提供されなかった、トークンが無効であるなど)、サーバーはServerFunctionNotFoundErrorをスローします。このエラーはRSCを通じてクライアントに伝播され、エラーバウンダリを使用してキャッチできます。
これによりアプリケーション内に存在するサーバ関数に関する情報が漏れるのを防ぎます。
暗号化は以下のセキュリティ特性を提供します。
- サーバ関数の隠蔽 — クライアントは暗号化されたトークンからサーバ関数のファイルパスや名前を特定できません。
- 機能保護 — クライアントはサーバーがレンダリング時に明示的に公開した関数、またはクライアントコードにバンドルされた関数のみを呼び出せます。任意のサーバ関数に対するトークンを偽造することはできません。
- トークンの一意性 — 各サーバ関数参照には一意の暗号化トークン(ランダムな初期化ベクトル)が付与され、トークンの予測を不可能にします。
- キーローテーション —
previousSecrets設定を使用することで、ダウンタイムや再構築なしに実行時にシークレットをローテーションできます。
暗号化はどのサーバ関数が呼び出し可能であるかを保護するものであり、それらの関数に渡される引数の内容まで妥当性を検証するわけではありません。サーバ関数の内部では、常に引数のバリデーションとサニタイズを行ってください。