ExpressJsでExpress-Validatorを使用してフォーム入力を効率的に処理する方法
著者は「Write for DOnations」プログラムの一環として、寄付を受けるためにOWASP財団を選びました。
紹介
現代のウェブ開発者は、ウェブフォーム、ファイルアップロード、またはウェブサイト上のその他の入力など、常に入力に対処しています。したがって、ユーザーから受け取る入力が正当で無害であることを確認することが彼らにとって不可欠になります。
ウェブ開発の進歩により、攻撃者はより多くの手法を開発し、フォーム入力など、さまざまな側面からウェブサイトを悪用する方法を見つけました。クロスサイトスクリプティング(XSS)やSQLインジェクションのような攻撃は、これまで以上に一般的で有害になっています。したがって、すべての開発者は、ユーザーから受け取るデータが悪意のないものであるか、整形されていることを確認し、ウェブアプリケーションおよびサーバーの整合性を確保する必要があります。
このチュートリアルでは、JavaScriptのExpress-Validatorパッケージを使用して、ウェブフォームのユーザー入力を検証およびサニタイズし、データの整合性とサーバーのセキュリティを確保する方法を学びます。
前提条件
このチュートリアルを正しく理解するためには、次のセットアップが必要です:
システムに14.x以上のNode Jsをインストールしてください。このDOのチュートリアルを使用して、システムに最新のNode Jsバージョンをインストールできます。
あなたはJavaScriptでコーディングする方法を知っているべきです。
システムに4.x以上のExpress JSバージョンがインストールされている必要があります。これを使用してExpressサーバーをセットアップできます。
これらの要件を満たした後は、チュートリアルを続けることができます。
ウェブ開発における入力検証の重要性
入力検証およびサニタイズは、以下に示すさまざまな理由から、開発者にとって重要な作業です。
インジェクション攻撃の防止: あなたのアプリケーションがSQLインジェクション攻撃に対して脆弱であるシナリオを考えてみましょう。この脆弱性は、認証フォームを通じてユーザー認証中にSQLコードの取り扱いが不十分であることから生じます。攻撃者は、ユーザーの認証情報の代わりに悪意のあるSQLコードを渡すことにより、サーバーへのアクセスを得ることができ、アプリケーションとしては終わりとなります。
データの整合性と一貫性: ユーザー入力が検証されると、サーバーに保存されるデータの一貫性が生まれ、データ処理が複雑になります。たとえば、ユーザーが年齢の入力としてテキストデータを送信できる場合、サーバーに保存されるデータに一貫性がなくなります。
データ互換性: 大規模な組織でデータがさまざまなエンドポイントで使用される際、データの種類は一貫している必要があります。たとえば、ユーザーがメールの資格情報に適切なメールの代わりにゴミデータを入力できる場合、組織がユーザーに連絡する必要がある際に問題が発生する可能性があります。
強化されたユーザーエクスペリエンス: 入力が検証されると、開発者はユーザーに適切かつ即時のフィードバックを送るためのロジックを作成できるため、ユーザーは提供した無効な入力を修正できます。これにより、全体的なユーザーエクスペリエンスが向上します。
開発者として、ウェブフォームやウェブサイトでの入力を効率的かつ安全に処理するための理由はたくさんあります。次のセクションでは、Express Jsアプリでのフォーム入力の検証ロジックをExpress-Validatorパッケージを使用して作成する方法を学びます。
入力検証とサニタイズで使用される用語
バリデーター – バリデーターは、入力を受け取り、いくつかの基準に基づいて特定のチェックを行う関数です。
サニタイザー - サニタイザーは、入力データを変更またはクリーンアップして、それが安全で必要な形式に従うことを保証するために使用される関数です。
バリデーションチェーン - Express-Validatorにおけるバリデーションチェーンは、入力に対して適用されるバリデーターやサニタイザーのシーケンスです。例えば、ユーザーにメールアドレスを入力させるフォームがあると仮定しましょう。そして、データベースのデータの一貫性を保つために、入力に余分な空白(前後に空白がないこと)を許可したくありません。このような場合、.isEmail()のようなバリデーションチェーンを使用して、this.trim()を達成できます(このことについては後のセクションで学びます)。これは、まず入力がメールであるかどうかをチェックします。もしメールデータであれば、trim()サニタイザーが入力の両端から空白を削除します。
Express-ValidatorをインストールしてExpressサーバーに統合する
さて、このチュートリアルの次のセクションでは、入力バリデーションとサニタイズ技術を実装する方法を学びます。まず、npmプロジェクトを設定し、その後、チュートリアルを進めるために必要なExpress Js、Express-Validator、およびその他の依存関係をインストールします。
Express サーバーを作成するには、まず npm プロジェクトを作成する必要があります。最初に、ターミナルを開いて次のコマンドを入力してください:
cd <project directory> npm init次に、アプリケーションに関する情報を入力するよう求められます。具体的な詳細を入力するか、‘Enter’を押し続けることができます。プロジェクトフォルダにindex.jsファイルを作成してください。これがサーバーのエントリーポイントになります。最後に、Expressサーバーを作成するためにExpress Jsパッケージをインストールする必要があります。これは以下のコマンドで行うことができます。
npm install expressあなたのプロジェクトにExpress Jsがインストールされました。次のタスクは、以下のコマンドで実行できる
Express-Validatorをインストールすることです。npm install Express-Validatorこれまでの基本的な依存関係は満たされていますが、
nodemonをプロジェクトにインストールすることができます。これは、エラーが発生してもNode Jsアプリを実行し続けるのに役立ちます。以下のコマンドを入力してインストールします。npm install nodemon
私たちは現在、すべての要件をインストールし、サーバーとフォームの入力検証に取り組むことができるようになりました。このプロジェクトは、後のすべてのセクションで使用される基本構造であり、各セクションでは異なるトピックが説明されます。
次のセクションでは、Express-Validatorがどのように内部で動作するか、また、さまざまなフィールドの入力検証を行うためにフォームでどのように使用できるかを説明します。
Express-Validatorにおける基本的なフォーム検証技術
Express-Validatorは、Express JSモジュールとValidator.jsモジュールによって提供されるミドルウェアの組み合わせであり、文字列データタイプ用のバリデーターとサニタイザーを提供します。Express-Validatorは、バリデーションチェーンを使用して入力データを検証する機能を提供します。
このセクションでは、前のセクションでセットアップしたNode Jsプロジェクトを使用して、Express-Validatorのバリデーターの使用方法を学びます。そのために、ユーザーが自分の資格情報(名前、メール、パスワード)を使ってウェブアプリケーションにサインアップできるようにするExpressサーバーにルートを作成します。その後、ユーザーがサーバーにサインアップするたびに入力を検証するためのミドルウェアを作成します。
index.jsファイルを開き、以下のコードを入力してください。
- const express = require("express");
- const app = express();
- const { body, validationResult } = require("express-validator");
-
- // Express.js middleware to use JSON objects
- app.use(express.json());
-
- app.post(
- "/signup",
- // using validation to verify valid inputs (MIDDLEWARE)
- [
- [
- body("name").notEmpty(),
- body("email").isEmail(),
- body("password").notEmpty(),
- ],
- ],
- async (req, res) => {
- const errors = validationResult(req);
-
- if (!errors.isEmpty()) {
- return res.status(400).json({ errors: errors.array() });
- }
-
- res.status(200).json({success:'Successful Sign Up!'})
- }
- );
-
- // Server Listening at port 3000
- const port = 3000;
- app.listen(port, () => {
- console.log(`Listening at http://localhost:${port}`);
- });
ここでは、Express Jsをインポートし、Expressアプリを作成します。そして、JavaScriptの分割代入を使用して、Express-ValidatorからbodyとvalidationResult関数をインポートします。
情報 body() メソッドは、ユーザーからのリクエストペイロード(req)からの入力データを検証および選択するための検証チェーンを作成するために使用されます。これは、POSTリクエストで送信されたデータなどです。 validationResult() メソッドは、HTTPリクエストの検証チェーンの結果をJavaScriptオブジェクトとして保存します。このオブジェクトが空であれば、ペイロードがすべての検証テストに合格したことを意味します。そうでない場合は、ペイロードに関する情報と満たされなかった基準が保存されます。
それから、Express JのJSONミドルウェアを使ってJSONオブジェクトを扱います。
最後に、HTTP - POSTリクエストを使用してExpressルートを作成します(GETやPUTなど、他のタイプのリクエストでも作業できます)。ここで作成したルートは、ウェブサーバー上のサインアップフォームへのルートを表します。このフォームはクライアント側のHTMLを使用して入力を受け取り、サーバーはHTML DOMのボディを使用してウェブフォームの入力フィールドにアクセスして処理します。
Express-Validatorのbody()メソッドは、引数として指定されたbody()メソッドと同じname属性を持つHTMLコンポーネントの値を取得します。つまり、body(“password”)は、name属性がpasswordであるHTML入力コンポーネントの値を取得します。
次に、これらの値に基づいて、Express-Validatorが提供するバリデーターを使用できます。この例では、isEmail()およびnotEmpty()の2つのバリデーターを使用しました。これらのバリデーターは、それぞれ入力がメールであるかどうかと空でないかを判断します。入力がバリデーターの基準に一致しない場合、Express-ValidatorはvalidationResult()の形式でオブジェクトとしてエラーをスローします。しかし、入力が適用されたバリデーターの基準に一致する場合、validationResult()オブジェクトは空になります。 これは、'/signup'ルートの関数定義内で使用され、ifステートメントを使用して簡単なチェックを作成するものです。validationResult()配列が空でないと仮定します。この場合、クライアントにレスポンスコード400のエラーが送信されます。それ以外の場合、この例では、クライアントに成功したサインアップのメッセージが送信されます(これは例示のためのものであり、入力データをデータベースに保存する、入力資格情報を使用してユーザーを認証するなど、任意の作業を実行できます)。次に、ローカルホストのポート3000でサーバーをホストします。
この例では、これらの基本的なバリデーターが使用されました。ただし、Express-Validatorパッケージが提供する他のバリデーターを使用することもできます。以下の表には、isEmail()やnotEmpty()と同様に使用できる一般的なバリデーターがいくつか示されています。
| バリデーター | 使用 |
|---|---|
| 日付です | 入力が日付オブジェクトであるかをチェックします。 |
| 空です | 入力が空の文字列かどうかをチェックします。 |
| isHash(アルゴリズム) | 入力がハッシュであるかどうかをチェックします。引数にはMD5やSHAなどのハッシュアルゴリズムを指定します。 |
| isJWT | 入力がJWT(JavaScript Webトークン)であるかどうかをチェックします。 |
| はURLです | 入力がURLであるかどうかをチェックします。 |
| ブール値です | 入力がブール値(真または偽)であるかどうかをチェックします。 |
| isNumeric | 入力が数値データタイプであるかをチェックします。 |
| アルファベットと数字が含まれています | 入力が英数字かどうかを確認します。 |
| isInt | 入力が整数型かどうかをチェックします。 |
| は小数です | 入力が10進数であるかどうかをチェックします。 |
| は浮動です | 入力が浮動小数点数かどうかをチェックします。 |
さて、次のセクションでは、フォームを通じてのファイルアップロード(写真やサインなど)を安全で制御されたものにするために、ファイル入力を検証する方法を学びます。
ウェブフォームにおけるファイル入力検証の処理(オプション)
前のセクションでは、Webフォームに一般的に渡される入力を検証するために Express-Validator を使用する方法を学びました。このセクションでは、ファイル入力を処理し、サーバーにアップロードする前にそれらを検証する方法を学びます。これにより、サーバーに悪意のあるファイルがアップロードされるのを避けることができます。このセキュリティ上の利点に加えて、ファイル入力サイズに制約を設けることで、大きなファイルがサーバーを占有しないようにすることができます。
私たちは、フォームを使用してファイルアップロードを処理するために、Node Jsのmulterパッケージを使用します。この広範なDOチュートリアルを使用して、multerがどのように機能するかを理解できます。Node.jsとExpressでのmulterを使ったファイルアップロード。さて、Express-Validatorはファイル入力処理のための特定の機能を提供していませんが、multerライブラリをExpress-Validatorの拡張として組み合わせることで、ウェブフォームのファイル入力検証を処理できます。multerを使用する前に、Node Jsプロジェクトにパッケージをインストールする必要があります。これは、コマンドに従って行うことができます。
cd <project folder>
npm install multer
さて、あなたのExpressサーバーにmulterをExpress-Validatorと統合する方法を見ていきましょう。次のコードでは、multerとExpress-Validatorを実装し、アップロードされたファイルにいくつかの制約を設けます。
- const express = require("express");
- const app = express();
- const { body, validationResult } = require("express-validator");
- const multer = require("multer");
-
- // Express.js middleware to use JSON objects
- app.use(express.json());
-
- // CREATING MIDDLEWARE USING MULTER TO HANDLE UPLOADS
-
- // creating a storage object for multer
- const storage = multer.diskStorage({
- // providing the destinations for files to be stored in server
- destination: "./uploads/",
- });
-
- // defining file storage to local disk and putting constraints on files being uploaded
- const upload = multer({
- storage: storage,
- limits: { fileSize: 1*1024*1024 }, // file size in bytes
-
- // you can add other constraints according to your requirement such as file type, etc.
- });
-
- app.post(
- "/fileUpload",
-
- // input validation for files to ensure only a single file is uploaded in one request
- upload.single("fileUpload"),
-
- // using validation to verify valid inputs
- [
- body("fileDescription").notEmpty(),
- // you can add as many validators as you require
- ],
- async (req, res) => {
- const errors = validationResult(req);
-
- if (!errors.isEmpty()) {
- return res.status(400).json({ errors: errors.array() });
- }
- const file = req.file;
- // perform any other tasks on the file or other form inputs
-
- res.status(200).json({ success: "Successful Sign Up!" });
- }
- );
-
- // Server Listening at port 3000
- const port = 3000;
- app.listen(port, () => {
- console.log(`Listening at http://localhost:${port}`);
- });
ここでは、ネットワーク上でJSONオブジェクトを扱うために必要なインポートとExpressのJSONミドルウェアを定義しました。それから、multerが機能するためのストレージサーバーを定義します。以下に例について詳しく説明します:
最初に、ストレージカテゴリを定義します。
Multerは、ディスクストレージとメモリストレージ(バッファ)を提供します。この例では、メモリを無駄にしないために、アップロードされたファイルをサーバーのローカルディスクに保存するためにディスクストレージを使用しています。ディスクストレージを使用する際には、アップロードされたファイルが保存されるディレクトリの場所を指定する必要があります。これは、パスを値として取るdestinationキーワードを使用することで行えます。ここでは、プロジェクトディレクトリ自体にあるuploadsフォルダを使用しています。
この後、ファイルのアップロードを処理するアップロードオブジェクトを作成します。これには
multerのインポートが呼び出され、JavaScriptオブジェクトが入力として渡されます。このJavaScriptオブジェクトには、入力ファイルをディスクに保存する前に満たすべきさまざまな制約/検証が含まれています。ストレージは必要な引数であり、これにより、前に定義されたストレージオブジェクトが入力として渡されます。1MBを超えるファイルがないことを確認するために、limitsオプションを使用して別の検証を追加しました。サイズはバイトで計算されることに注意してください。
情報: 要件に応じて、limitsオブジェクト内で他の検証/チェックを行うことができます。
次に、ファイルアップロードを処理するためのExpressルートを作成し、ミドルウェア
upload.single()を追加します。これにより、一度のリクエストで一つのファイルのみがアップロードされることが保証されます。これは、HTML DOMから必須引数としてfileUploadという名前の入力フィールドを取得します。これに続いて、前のセクションで説明したように、Express-Validatorミドルウェアを追加します。
最後に、ルートを処理するロジックを作成し、入力検証でのエラーをチェックします。
アップロードされたファイルにアクセスするには、req.fileオブジェクトからルートロジックを使用します。
ここでは、Multerを使用してファイルの入力検証を処理する方法と、それをExpress-Validatorの検証ミドルウェアと統合する方法を学びました。さらに、MulterやExpress-Validatorに多くの他の高度なまたはカスタム検証方法を追加することができます。次のセクションでは、SQLインジェクションのような攻撃を防ぐために必要な入力値のサニタイズ方法を学びます。
フォーム入力の sanitization
これまでに、データとファイル入力の検証を行う方法を学びました。次に、検証された入力をサニタイズする方法を学ぶことで、その知識を拡張します。Express-Validatorは、Validator.jsライブラリから多くのサニタイザーを提供しています。
サニタイザーの説明のために、Express-Validatorにおける基本的なフォーム検証技術と同様のアプローチを使用し、ルートにサニタイザーを追加します。
- const express = require("express");
- const app = express();
- const { body, validationResult } = require("express-validator");
-
- // Express.js middleware to use JSON objects
- app.use(express.json());
-
-
- app.post(
- "/sanitizedInput",
- // sanitizing inputs
- [
- [
- body("name").notEmpty().trim(),
- body("email").isEmail().trim(),
- body("dob").toDate(),
- ],
- ],
- async (req, res) => {
- const errors = validationResult(req);
-
- if (!errors.isEmpty()) {
- return res.status(400).json({ errors: errors.array() });
- }
- // remaining logic for your route
- res.status(200).json({success:'Successful Sign Up!'})
- }
- );
-
- // Server Listening at port 3000
- const port = 3000;
- app.listen(port, () => {
- console.log(`Listening at http://localhost:${port}`);
- });
必要なパッケージをインポートし、Express JSONミドルウェアを使用してJSONオブジェクトを処理することで、別のExpressサーバーを作成します。次に、サニタイズされた入力(/sanitizedInput)を処理するルートを作成し、そのルートロジックを記述します。
再度、Express-Validatorミドルウェアを追加する必要があります。ここでは、3つの入力フィールドを使用します:name、email、およびdob。最初に、notEmpty()を使用して名前が空でないかを確認し、その後trim()サニタイザーを適用して、入力値の両端から空白を削除します。同様に、メール入力フィールドについては、最初にisEmail()を使用して有効なメールであるかを確認し、その後trim()サニタイザーを適用して両端の空白を削除します。
このdob入力フィールドでは、入力文字列を日付オブジェクトに変換するために、toDate()サニタイザー(バリデーターなし)を使用します。入力文字列が実際の日付でない場合、toDate()はnull値を返します。
その後、validationResult()オブジェクトにエラーが格納されていないことを確認するチェックを行い、ルートの残りのロジックを続行できます。
注意: body("name").notEmpty().trim() のように単一の入力に対して複数のバリデーターやサニタイザーを使用することは、バリデーター/サニタイザーのチェーンを作成します。このようなチェーン、またはバリデーター/サニタイザーの連続使用は、バリデーションチェーンを生成します。
Express-Validatorには、blacklist()、whitelist()など、さらに多くのサニタイザーが用意されています。必要に応じてそれらを使用することができます。
結論
このチュートリアルでは、サーバー上でフォーム入力を効果的に処理する方法を学びました。Express-Validatorライブラリを使用して、入力バリデーションおよびサニタイズ手法を用いたサーバーを構築しました。この知識を基に、同じライブラリが提供するさまざまなバリデーターやサニタイザーを使用して、入力バリデーションをさらに拡張することができます。
Express-Validator と Multer の両方は、カスタムバリデーターを作成することを可能にします。これを練習として試すことで、複雑なプロジェクトでの生産性を向上させることができます。
