ウェブサイト検索

Express Apps でのユーザー認証の実装


正規のユーザーを認証することで、悪意のあるユーザーから保護できます。セキュリティ ホールを残さないように、必ずベスト プラクティスを使用してください。

ユーザー認証は、アプリケーションにアクセスしようとするユーザーの身元を確認するプロセスです。これには、ユーザーの信頼性を確認するための資格情報の承認と転送が含まれます。

Express、Bcrypt、MongoDB を使用して、わずか数ステップで簡単なユーザー認証モデルを Node.js に実装できます。

ステップ 1: 開発環境のセットアップ

まず、プロジェクト フォルダーを作成し、次のコマンドを実行してそこにcdします。

mkdir user-authentication
cd user-authentication

次に、次のコマンドを実行して、プロジェクト ディレクトリ内の npm を初期化します。

npm init -y

-y フラグは npm を初期化し、すべてのデフォルトを使用して package.json ファイルを作成します。

このユーザー認証モデルにはいくつかの依存関係が必要です。

それらには次のものが含まれます。

  • Express: Express は、Web およびモバイル アプリケーションに堅牢な機能セットを提供する Node.js フレームワークです。 Node.js を使用したバックエンド アプリケーションの構築が容易になります。
  • Bcrypt: bcrypt は、bcrypt パスワード ハッシュ関数を実装する npm パッケージです。これにより、プレーンなパスワード文字列からハッシュを作成できます。
  • Mongoose: Mongoose は、MongoDB オブジェクト データ モデリング ライブラリです。これにより、アプリと MongoDB データベース間のやり取りが簡素化されます。
  • dotenv: dotenv は、環境変数を .env ファイルから process.env にロードする依存関係のないパッケージです。
  • Validator: validator は、さまざまな文字列検証関数が含まれるパッケージです。
  • Body-parser: body-parser パッケージは、ハンドラーの前にミドルウェア内のリクエスト本文を解析します。

以下を実行してパッケージをインストールします。

npm install express bcrypt mongoose dotenv validator body-parser

次に、プロジェクトのルート ディレクトリにapp.js ファイルを作成し、以下のコード ブロックを追加して基本的な Express サーバーを作成します。

// app.js
const express = require('express');
const app = express();
const bodyParser = require("body-parser");
 
const port = 3000;
 
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
 
app.listen(port, ()=>{
    console.log(`App is listening on port ${port}`);
});

このコードは、express 関数を呼び出して Express アプリケーション インスタンスを作成します。次に、body-parser ミドルウェアを使用して、受信したリクエストの本文を解析します。次に、Express インスタンスの listen メソッドを呼び出し、ポート変数を引数として渡すことにより、ポート 3000 でトラフィックの待機を開始します。

ステップ 2: アプリケーションをデータベースに接続する

プロジェクトのルート ディレクトリに .env ファイルを作成し、MongoDB 認証情報をそのファイルに保存します。これにより、悪意のあるユーザーにデータベースへのアクセスを許可する可能性のあるコード内でデータベースの資格情報が公開されることが回避されます。

次に、app.js ファイルに移動し、mongoose をインポートします。

const mongoose = require("mongoose");

次に、 import dotenv を呼び出し、その上で config メソッドを呼び出します。

require("dotenv").config();

dotenvconfig メソッドを呼び出すと、環境変数が process.env にロードされます。

最後に、mongoose で connect メソッドを呼び出し、MongoDB URI を引数として渡します。

mongoose.connect(process.env.MONGODB_URI).then(() => {
    console.log('Connected to Database Successfully')
})

ステップ 3: ユーザーモデルの作成

プロジェクトのルート ディレクトリに、「models」フォルダを作成します。ここにマングース モデルを保存します。

mkdir models

次に、「userModel」ファイルを作成し、次のインポートを追加します。

const mongoose = require('mongoose')
const { isEmail } = require('validator')

isEmail は、指定された文字列が電子メールの場合に true を返す検証関数です。マングース検証をユーザー モデルに適用するために必要になります。

次に、次のコードをuserModel ファイルに追加します。

// models/userModel
const userSchema = mongoose.Schema({
    email: {
        type: String,
        required: [true, 'Email is required'],
        validate: {
            validator: isEmail,
            message: props => `${props.value} is not a valid email`
        }
    },
 
    password: {
        type: String,
        required: [true, 'Password is required'],
        validate: {
            validator: function (value) {
                return value.length >= 6
            },
            message: () => 'Password must be at least six characters long'
        }
    }
})
 
module.exports = mongoose.model('User', userSchema)

上記のコードは、mongoose.Schema メソッドの値を格納する userSchema 変数を作成します。 mongoose.Schema メソッドは、プロパティを MongoDB コレクションにマップし、そのコレクション内のドキュメントの形状を定義します。 mongoose スキーマには、メールパスワードの 2 つのプロパティがあり、これらが認証要件になります。

email プロパティは文字列タイプであり、必須が true に設定されています。リクエスト本文に電子メールプロパティが含まれていない場合、付随するエラー メッセージ「電子メールが必要です」が表示されます。最後に、mongoose カスタム検証を使用して、validator プロパティがisEmail 関数を参照します。この関数は、電子メールとしての文字列の有効性に基づいて true または false を返します。次に、message プロパティは電子メールの値 (props) を受け取り、意味のあるエラー メッセージを構築します。

パスワード プロパティは必須の文字列タイプであり、「パスワードが必要です」というエラー メッセージが表示されます。 バリデータ関数は、パスワードが 6 文字以上の場合に true を返す匿名関数です。

最後の行では、mongoosemodel メソッドを呼び出して、mangoose モデルを作成してエクスポートします。最初の引数としてモデル名 (User) を渡し、 2 番目の引数としてスキーマ (userSchema) を指定します。

ステップ 4: サインインおよびサインアップ ルートの実装

プロジェクトのルート ディレクトリに、routes フォルダーを作成します。

mkdir routes

ルート フォルダーにuserRoutes.js ファイルを作成し、次のインポートを追加します。

// routes/userRoutes.js
const express = require("express");
const User = require("../models/userModel");
const bcrypt = require("bcrypt");

expressRouter メソッドを呼び出して、Express Router インスタンスを作成します。

const router = express.Router();

次に、以下のコード ブロックを userRoute.js ファイルに追加して、サインアップ ルートを作成します。

router.post("/sign-up", async (req, res) => {
  try {
    // Extract email and password from the req.body object
    const { email, password } = req.body;
    
    // Check if the email is already in use
    let userExists = await User.findOne({ email });
 
    if (userExists) {
      res.status(401).json({ message: "Email is already in use." });
      return;
    }
 
    // Define salt rounds
    const saltRounds = 10;
 
    // Hash password
    bcrypt.hash(password, saltRounds, (err, hash) => {
      if (err) throw new Error("Internal Server Error");
 
      // Create a new user
      let user = new User({
        email,
        password: hash,
      });
 
      // Save user to database
      user.save().then(() => {
        res.json({ message: "User created successfully", user });
      });
    });
  } catch (err) {
    return res.status(401).send(err.message);
  }
});

上記のコード ブロックでは、まず、req.body オブジェクトから電子メールとパスワードを構造化解除しました。次に、電子メールはユーザーごとに一意である必要があるため、ユーザーがすでにその電子メールを使用しているかどうかを確認します。電子メールがすでに使用されている場合は、401 ステータス コードを返してコードの実行を停止します。

プレーンなパスワードをデータベースに保存すると、悪意のあるハッカーがデータベースにアクセスできる可能性があるため、セキュリティ上の大きな脅威となります。パスワードをデータベースに格納する前にハッシュ化する必要があります。そのため、ハッカーにパスワードが発見されたとしても、ユーザーに危険が及ぶことはありません。ハッシュ化は、指定された「キー」を別の値に変換するプロセスです。ハッシュ化は一方向関数です。つまり、暗号化とは異なり、ハッシュ化された値から元の値を取得することはできません。

bcrypt を使用すると、bcrypt のハッシュ メソッドを呼び出してユーザー パスワードをハッシュします。ハッシュ メソッドは、ハッシュされる文字列、ソルト ラウンド、コールバック関数の 3 つのパラメータを取ります。ユーザーのパスワード、前に作成した SaltRounds 変数、およびコールバックを渡します。

ソルト ラウンドとは、単一の bcrypt ハッシュを計算するのに必要な時間を指します。ソルト ラウンドの数が多いほど、ハッシュ ラウンドの数も多くなります。

ハッシュ メソッドがエラーをスローすると、「内部サーバー エラー」がスローされます。それ以外の場合は、パスワード プロパティを成功したハッシュに設定し、ユーザー インスタンスの save メソッドを呼び出してデータベースに保存します。

次に、以下のコード ブロックを userRoute.js ファイルに追加して、サインイン ルートを作成します。

router.post("/sign-in", async (req, res) => {
  try {
    // Extract email and password from the req.body object
    const { email, password } = req.body;
 
    // Check if user exists in database
    let user = await User.findOne({ email });
 
    if (!user) {
      return res.status(401).json({ message: "Invalid Credentials" });
    }
 
    // Compare passwords
    bcrypt.compare(password, user.password, (err, result) => {
      if (result) {
        return res.status(200).json({ message: "User Logged in Successfully" });
      }
      
      console.log(err);
      return res.status(401).json({ message: "Invalid Credentials" });
    });
  } catch (error) {
    res.status(401).send(err.message);
  }
});
 
module.exports = router;

上記のコード ブロックでは、まず、req.body オブジェクトから電子メールとパスワードを構造解除します。次に、データベースにユーザーが存在するかどうかを確認します。ユーザーがデータベースに存在しない場合は、401 ステータス コードが返されます。

次に、bcrypt の比較メソッドを使用して、ユーザーが指定したパスワードとデータベースから取得したハッシュ化されたパスワードを渡します。 2 つを比較して、一致するかどうかを確認します。パスワードが一致すると、ステータス コード 200 と成功メッセージが返されます。それ以外の場合は、401 ステータス コードとエラー メッセージが返されます。

最後に、ルーターapp.js ファイルにインポートし、アプリケーションレベルのミドルウェアとして使用します。

これでユーザー認証モデルが完成しました。これで、ユーザーは安全にサインアップしてアプリケーションにサインインできるようになりました。

ユーザー認証の重要性

ユーザー認証により、正当なユーザーのみがアプリケーションにアクセスできるようになります。データが何らかの形で個人的または非公開である場合は、認証されていないユーザーがアクセスできないようにする措置を講じる必要があります。

関連記事: