ReactとTypeScriptのセキュリティに関する5つのベストプラクティス
Marcelo Oliveira
2022年12月8日
0 分で読めますReactは、フル機能のフレームワークではなく、ユーザーインターフェースの構築に特化したライブラリです。そのため開発者は、ルーティング、履歴管理、認証など、アプリケーションのさまざまな要素に好みのライブラリを選択できます。一方、MicrosoftがJavaScriptの拡張として開発したTypeScriptは、型付けが緩やかなJavaScriptに任意の静的型付けを導入します。
ReactでTypeScriptを使うと、よりシンプルなReactコンポーネントを選べるほか、静的型検証のためのJavaScript XML(JSX)サポートが強化されるなど、アプリケーション開発にいくつものメリットがあります。TypeScriptプロジェクトではJavaScriptコンポーネントも利用できるため、JavaScriptの経験がある開発チームは、その知識を活かして強い型付けを取り入れられます。
Reactプロジェクトを始めるためのボイラープレートは数多くあります。Create React App、Create Next App、Vite、React Boilerplate、React Starter Kitなどがその例です。
Create React Appは、npmまたはYarnで実行できるスタンドアロンのツールです。インストールすれば、わずかなコマンドで新しいプロジェクトを作成して実行できます。
まず、ターミナルを開き、次のコマンドを実行してCreate React Appをインストールします。
次に、node package execute (npx)を使って、TypeScriptテンプレートからプロジェクトを作成します。
または、Yarnパッケージマネージャーを使ってプロジェクトを作成することもできます。
潜在的なセキュリティリスクと、その軽減方法を見ていきましょう。
strictモードを有効にする
strictモードを有効にすると、データ型のルールに関するTypeScriptコンパイラのパラメーターが自動的に有効になります。TypeScriptのstrictモードでは、厳格な型ルールに基づいてコードが検証されます。これにより、開発者は変数、定数、パラメーター、関数の戻り値に割り当てられたデータ型の制約に従ってコードを書く必要があります。開発の早い段階で型の不一致によるバグを発見し、修正できるため、strictモードは重要です。
次の関数があるとします。
deleteComment関数を呼び出す際、slug引数には文字列を、commentIdには数値を渡します。
しかし、2つの文字列を渡して関数を呼び出すこともできてしまいます。
上記のコードはビジネスルール上許容されないかもしれません。しかし型を指定していないため、両方のパラメーターがデフォルトのany型と見なされます。その結果、strictモードを有効にしない限り、TypeScriptは問題を検出できません。
新しく作成したReactアプリケーションでは、strictの値はtrueに設定されています。ただし、既存のプロジェクトでは設定が異なる場合があります。
TypeScriptプロジェクトがstrictモードで動作していることを確認するには、tsconfig.jsonファイルを開き、strict設定の値がtrueになっていることを確認します。
deleteComment関数に戻りましょう。少し変更するだけで、TypeScriptのコンパイルエラーが発生します。

deleteCommentのパラメーターに、文字列型と数値型を追加します。
この変更によりコンパイルエラーが発生するため、次の例のように、無効な型でdeleteComment関数を誤って呼び出すクライアントコードの作成を防げます。

strictオプションを有効にすると、より厳格な型チェックに関する、その他の推奨コンパイラオプションも自動的に有効になります。
戻り値を使わないコールバックにany型を指定しない
コールバックの戻り値をany型として宣言すると、関数が値を返さないにもかかわらず、その戻り値を誤って使ってしまい、問題が見逃される可能性があります。
onListFieldKeyUpという関数を考えてみましょう。この関数は、onEnterというコールバックをパラメーターとして受け取ります。
上記のenterResult変数には、後で使うためにonEnterコールバック関数の結果が格納されます。コールバックは値を返しませんが、enterResult変数をany型として宣言しているため、TypeScriptは問題を知らせることができません。
どうすればよいでしょうか。
まず、onEnter関数が値を返さないと分かっている場合は、コールバックパラメーターのany型をvoidに置き換えます。するとTypeScriptは、「'void' 型の式は真偽値としてテストできません」というエラーを表示します。

最後に、onEnterの戻り値をenterResult変数に格納するコードを削除します。
Reactにおけるサーバーサイドレンダリング攻撃
Webアプリケーションは、クライアント側またはサーバー側でHTMLをレンダリングできます。Reactのような最新のJavaScriptフレームワークやライブラリでは、サーバーサイドレンダリングが採用されています。この手法は、ページの読み込みを高速化するなど、パフォーマンス面でメリットがあります。バックエンドでページ全体をすばやく事前レンダリングし、静的なHTML、CSS、JavaScriptのコンテンツをフロントエンドに渡せるため、ユーザーはWebページをすぐに表示し、操作できます。また、読み込みの速いページは検索アルゴリズムで高い評価を得られるため、検索エンジン最適化(SEO)にも役立ちます。
クロスサイトスクリプティング(XSS)は、攻撃者が悪意のあるクライアント側スクリプトをWebページに注入する攻撃手法です。ReactはXSSから安全に保護されるよう設計されています。しかし、不適切なプログラミングやReactでのサーバーサイドレンダリングによってXSSの脆弱性が生じ、悪意のあるユーザーに悪用される可能性があります。
たとえば、サニタイズされていないデータをrenderToStaticMarkup関数の出力と連結してからクライアントに送信することは、絶対に避けてください。
ハッカーがsomeUnsanitizedData変数を改ざんして、次のような悪意のあるJavaScriptコードを含める可能性があるため、上記のコードは安全ではありません。
XSS攻撃を防ぐには、DomPurifyなどのHTMLサニタイザーを使用してください。
不透明型を使う
不透明なデータ型は、情報の隠蔽を実現します。そのデータ構造はインターフェースで定義されないため、具体的なデータ型の実装を隠蔽し、カプセル化できます。外部モジュールは内部にアクセスせずに不透明型を使えますが、隠された情報にアクセスできる内部関数はその型を操作できます。不透明型を使えば、利用側のコードを変更せずに内部の詳細を変更、発展させられます。そのため、これを使うことは開発のベストプラクティスです。
TypeScriptには標準で不透明型が用意されていませんが、簡単に実装できます。実際のユースケースを解決してみましょう。
TypeScriptの関数を使って、顧客のカートに商品を追加するECアプリケーションを考えてみましょう。
これらの型付きパラメーターにより、プログラマーがcustomerCodeとproductCodeのパラメーターに文字列以外の数値や型を誤って指定するのを防げます。
このコードに問題があるわけではありませんが、型が値の意味を正確に表現できていません。コードを見ただけでは、どちらのパラメーターが顧客コードで、どちらが商品コードなのか分かりません。また、値を入れ替えてもTypeScriptは警告を出しません。
次の例では、addToCartをリファクタリングして、不透明型を使っています。
新しい不透明型と正規表現による検証を使うと、誤ったコードを指定した場合にコンパイラエラーが発生します。
dangerouslySetInnerHTMLを使う場面と、適切なサニタイズを行う方法
Reactは軽量な手法として仮想DOMを使用し、ページのHTMLを効率的に更新します。これにより、HTML要素を操作するために、ユーザーがブラウザーのネイティブAPIを直接扱う必要がなくなります。
ただし、Reactアプリケーションでこの仕組みを上書きし、生のHTMLコードを設定しなければならない場合があります。このHTMLコードを直接操作するために、ReactではdangerouslySetInnerHTMLという特別なコンポーネントプロパティを使います。
名前が示すとおり、dangerouslySetInnerHTMLプロパティは、適切に使わないとアプリケーションを脆弱にします。dangerouslySetInnerHTMLに依存するアプリケーションは、ハッカーに悪用されてクロスサイトスクリプティング(XSS)攻撃を受け、信頼できるユーザー入力を装った悪意のあるスクリプトがWebサイトに注入されるおそれがあります。
このリスクを防ぐには、HTMLコンテンツを挿入する前に必ずサニタイズし、不純物や悪意のあるコードを取り除いてください。DOMPurifyのようなライブラリを使い、sanitize関数を適用できます。
他のライブラリと同様に、DOMPurifyにもセキュリティ脆弱性が見つかることがある点に注意が必要です。幸い、Snyk Vulnerability Databaseにはこの脆弱性情報がすぐに反映され、解決策も提供されました。実績のあるライブラリであっても、Snykのようなツールで定期的にスキャンし、既知の脆弱性がないか確認するなど、引き続き適切な注意を払う必要があります。
開発者が考慮すべきセキュリティ上の懸念は、これだけではありません。この動画では、Liran Talが、React開発者のミスがどのように他の脆弱性につながり、ハッカーに悪用されるのかを解説します。

まとめ
この記事では、TypeScriptを使ったReactアプリケーション開発のベストプラクティスを紹介し、潜在的なセキュリティリスクとその解決策について説明しました。
strictモードを使えば、型の制約を適用し、開発の早い段階で型の不一致によるミスを発見できます。コールバックで
voidを使うと、値を返さないコールバックの戻り値を開発者が使ってしまうのを防げます。コマンドインジェクション攻撃は、深刻なセキュリティ脅威です。不審なユーザー入力を使って動的なコードを組み立てることは避け、
execではなくexecFile関数を使用してください。HTMLインジェクションも、深刻なセキュリティ脅威です。
dangerouslySetInnerHTMLを使う場合は、攻撃者によるユーザー入力の改ざんや悪意のあるコードの注入を防ぐため、挿入するマークアップを必ずサニタイズしてください。最後に、不透明型を使うと意味のあるドメインを表現でき、型の検証や重複の回避も容易になります。
TypeScriptとReactを使えば、高速で安全なアプリケーションを簡単に構築できます。特に、Create React Appなどのフレームワークを使い、既存のJavaScriptの知識を活かすと効果的です。



