JavaScriptでBcryptsJSを使用して安全にパスワードを処理する方法

導入

ウェブサイトのパスワードを保護することは、どんな開発者にも必要なスキルです。 JavaScriptは、JavaScriptBcryptJSモジュールが提供するハッシュアルゴリズムを使用して、パスワードやその他の機密データの安全な保存と処理を保証するオプションを提供します。

このチュートリアルでは、BcryptJSとハッシュ化について学び、パスワードを生の文字列ではなくハッシュとしてデータベースに保存し、認証のためにそれらを取得する基本的なエクスプレスサーバーを設定します。

前提条件

このチュートリアルを続けるには、以下のセットアップが必要です。

  1. コンピュータに安定したバージョンのNode.jsをインストールしておく必要があります。バージョンは12.x以上である必要があります。最新のNode.jsバージョンをインストールするために、こちらのDigitalOceanのチュートリアルを使用できます。

  2. JavaScriptでコーディングする方法を知っている必要があります。

  3. _コンピュータにExpress JSをインストールしておく必要があります。Expressサーバーのセットアップ方法については、こちらのガイドを使用することができます。

  4. 最後に、このチュートリアルを完了するためには、MongoDB CommunityまたはAtlasデータベースが必要です。MongoDBのインストール方法については、次のいずれかのDigitalOceanガイドを使用してインストールできます_ MongoDBのインストール方法

BcryptJSを使用する理由はなぜですか?

Bcryptは、パスワードのハッシュを作成してデータの漏洩の場合に保管するためのハッシュ化アルゴリズムです。この高度なハッシュ化アルゴリズムは、ソルトを使用しており、ブルートフォース攻撃などの攻撃に対して耐性があります。

BcryptJSは、BcryptハッシュアルゴリズムのJavaScript実装であり、複雑なハッシュ関数に関与することなくハッシュ暗号化を使用することができます。BcryptJSがパスワードセキュリティにおいて優れた選択肢となる理由の一部は以下の通りです:

  1. セキュリティ – BcryptJSはBcryptアルゴリズムを実装しており、遅いアルゴリズム(ハッシュにおいては良いもの)であり、強力な計算能力が必要です。これにより、攻撃者がパスワードハッシュをクラックすることは困難になり、データ侵害の場合でもパスワードの安全性を保証します。

  2. ソルティング – BcryptJSはパスワードのランダムソルトの生成を処理し、ストレージのセキュリティを確保します(ハッシュとソルトについては次のセクションで詳しく学びます)。ソルトは比較的弱いパスワードのハッシュを複雑化し、解読を困難にします。

  3. 使いやすさ – BcryptJSはJavaScript開発者に対して、ハッシュに対する深い理解を必要とせずにパスワードを暗号化するためのツールを提供します。

次のステップでは、ハッシュソルトについて簡単に説明します。

ハッシングの仕組みは?

これらの概念をプロジェクトで使用する前に、ハッシングとソルティングの仕組みを理解する必要があります。

ハッシング

ハッシングとは、単純な文字列や平文をランダムな文字列(暗号化)に変換することです。これにより、機密データの安全な保存や送信が可能になります。ハッシングには以下の主要なステップが含まれます:

データ入力 – 最初に、バイナリ、文字、10進数などの任意のタイプのデータがプレーンテキストまたは文字列として保存されます。

ハッシュ関数 – ハッシュ関数は、データから入力を受け取り、文字列またはハッシュコードのセットに変換する数学的アルゴリズムです。ハッシュ関数は決定論的(同じ入力に対して同じ出力を生成する)であり、ワンウェイ関数です(つまり、ハッシュ関数の出力を入力データに戻すことはほぼ不可能です)。

衝突耐性 – これは、ハッシュ関数が衝突耐性の考えを念頭に置いて作成されることを意味します。つまり、2つの異なる入力は同じ出力(ハッシュコード)を持つことはありません。

認証 – ハッシュ関数は決定論的であり、同じ入力に対しては常に同じハッシュを生成します。したがって、パスワードをハッシュとして保存した場合、一般的な考え方は、認証するパスワードがデータベースに保存されているハッシュと一致する場合、パスワードが正しいということです。

ソルティング

ハッシュは何十年も前から存在しており、さまざまなハッシュアルゴリズムに基づいたデータ文字列とそれに対応するハッシュを含む数十億のデータエントリを含むレインボーテーブルなどの開発が行われてきました。

さて、ウェブサイトでアカウントを作成するユーザーが弱いパスワードを作成する状況を考えてみましょう。そのため、データの漏洩が発生した場合、攻撃者はユーザーのハッシュを検索し、弱いパスワードを持つユーザーアカウントのハッシュと一致するものを見つけることができます。これは、高セキュリティのアプリケーションでは壊滅的な結果となるでしょう。このような事態を防ぐために、ソルトが使用されます。

ソルトは、パスワードのハッシュをデータベースに保存する前に、ランダムな文字列をハッシュに追加することで、ハッシュに対して追加のセキュリティレイヤーを提供します。したがって、データが漏洩した場合でも、攻撃者がソルトを含むハッシュを解読することは困難になります。以下に例を示します。

  1. Password = ‘sammy’
  2. Hash = £%$^&£!23!3%!!
  3. Salt = 2vqw£4Df$%sdfk
  4. Hash + Salt = £%$^&£!23!3%!!2vqw£4Df$%sdfk

明らかにわかるように、ハッシュの決定論的な性質により、ソルトで保存されたパスワードは解読することが困難です。したがって、例えば、攻撃者がこのハッシュ+パスワードの文字列をレインボーテーブルで検索しても、実際のパスワードの文字列ではなく、完全に異なるものが得られます。

さて、BcryptJSを使ってパスワードを業界標準の方法で安全に保護する準備が整いました。

BcryptJSおよび他の必要なモジュールのインストール

ハッシュ化とソルトについて知ったので、あとはコンピュータを手に入れてコードを書くだけです。プロジェクトの構造は以下のようになります:

Project Structure

まず、次の手順でnpmプロジェクトを作成します:

  1. フォルダを開き、app.jsというファイルを作成します。

  2. このフォルダでターミナルウィンドウを開き、次のコマンドを入力します。

npm init

その後、入力を求められますが、何も入力せずにEnterキーを押すことができます。

  1. 次に、auth.js
  • db.js
  • User.js
  • という3つのファイルを作成します。
  1. 同じターミナルウィンドウで、次のコマンドを入力して必要なパッケージをインストールします。
npm install express mongoose BcryptJS nodemon

このチュートリアルでは、完全なプロジェクト環境が設定されました。次のステップでは、BcryptJSを使用してパスワードを安全に保存および認証するためのサーバーの作成方法を学びます。

Express JSを使用したサーバーのセットアップ

プロジェクトの構造が設定されたので、ハッシュとしてパスワードを保存し、認証するためにbcrytjsを使用するサーバーを作成できます。次の適切な手順でサーバーを作成します。

ステップ1 – MongoDBデータベースへの接続の作成

mongoDBに接続するために、コミュニティ版を使用します。プロジェクトを整理するために、接続の設定コードをdb.jsファイルに保存します。

  1. const mongoose = require("mongoose");
  2. const mongoURI = "mongodb://127.0.0.1:27017/bcrypt_database";
  3. const connectMongo = async () => {
  4. try {
  5. await mongoose.connect(mongoURI);
  6. console.log("Connected to MongoDB!");
  7. } catch (error) {
  8. console.error("Error connecting to MongoDB: ", error.message);
  9. }
  10. };
  11. module.exports = connectMongo;

ここでは、mongooseパッケージをインポートして、JavaScriptをMongoDBに接続するAPIを提供します。また、この場合、接続URIはローカルインストールのmongoDB用であり、クラウドデータベース(Atlasなど)を使用している場合は、URIを特定のデータベースのURIに変更するだけです。

情報
URIは、リソースの名前と識別子、およびインターネット上の場所を識別することができるサーバーのURLと似ています。一方、URLはURIのサブセットであり、後者のみを実行することができます。

connectToMongo関数は、mongoose.connectJavaScriptのプロミスを返すため、非同期関数です。この関数は正常に実行された場合、接続を返します。それ以外の場合はエラーを返します。

最後に、db.jsモジュールがインポートされるたびに、この関数をエクスポートするためにmodule.exportsを使用します。

ステップ2 – ユーザースキーマの作成

MongoDBデータベースでユーザーを作成または認証するために、基本的なスキーマが必要です。スキーマが何であるかわからない場合は、この優れたDOガイドを使用してMongoDBでスキーマを理解して作成することができます。スキーマでは、emailpasswordの2つのフィールドのみを使用します。

次のコードをUser.jsモジュールに使用してください。

  1. const mongoose = require("mongoose");
  2. const UserSchema = new mongoose.Schema({
  3. email:{
  4. type:String,
  5. required:true,
  6. unique:true
  7. },
  8. password:{
  9. type:String,
  10. required:true
  11. }
  12. });
  13. module.exports = mongoose.model('user', UserSchema)

このスキーマには、emailpasswordの2つのフィールドがあります。どちらも必須フィールドであり、文字列のデータ型です。また、emailはユニークフィールドであり、つまりこのサーバー上でアカウントを持つために1つのメールアドレスしか使用できません。

最後に、usersという名前のモデルをエクスポートします。モデルはデータベースのスキーマを表します。スキーマはモデルを定義するためのルールと考えることができ、モデルはMongoDBデータベース内のコレクションとして保存されます。

スキーマをモデルに変換するには、mongooseライブラリのmodel()関数を使用します。

ステップ3 – app.jsでサーバーをセットアップする

前の手順に従った後、モデルとMongoDBデータベースへの接続用のモジュールを正常に作成しました。次に、サーバーのセットアップ方法を学びます。以下のコードをapp.jsファイルに使用してください。

  1. const connectToMongo = require("./db");
  2. const express = require("express");
  3. const app = express();
  4. connectToMongo();
  5. app.use(express.json());
  6. app.use("/auth", require("./auth"));
  7. const port = 3300;
  8. app.listen(port, () => {
  9. console.log(`Listening at http://localhost:${port}`);
  10. });

ここでは、expressdb.jsモジュールをインポートしてMongoDBに接続します。次に、JSONレスポンスを処理するためにexpress.json()ミドルウェアを使用します。ルートは別のモジュール(auth.js)で作成され、コードをきれいかつ整理された状態に保ちます。最後に、サーバーがローカルホストのポート3300でリッスンするエンドポイントを作成します(任意のポートを使用できます)。

パスワードの暗号化とMongoDBデータベースへの保存

この時点では、サーバーはほぼ準備ができており、今度はサーバーのためのエンドポイントを作成します。2つのエンドポイント、signuploginを作成します。signupエンドポイントでは、新しいユーザーからメールアドレスとパスワードを取得し、BcryptJSを使用してパスワードを暗号化して保存します。

auth.jsファイルに以下のコードを入力してください:

  1. const express = require("express");
  2. const router = express.Router();
  3. const User = require("./User");
  4. const bcrypt = require("bcryptjs");
  5. // ROUTE 1:
  6. router.post("/signup", async (req, res) => {
  7. const salt = await bcrypt.genSalt(10);
  8. const secPass = await bcrypt.hash(req.body.password, salt);
  9. let user = await User.create({
  10. email: req.body.email,
  11. password: secPass,
  12. });
  13. res.json({ user });
  14. });
  15. module.exports = router;

必要なインポートを行い、expressルーターを設定して/signupエンドポイントを作成します。アプリケーション内でURLに資格情報が表示されないように、POSTメソッドを使用しています。その後、scriptsパッケージのgenSalt関数を使用してSaltを作成します。genSalt()関数に渡されるパラメータは、Saltの文字数です。次に、BcryptJSのhash()関数を使用し、ハッシュコードに変換する必要なパラメータであるパスワード文字列と、オプションの引数であるSalt文字列を指定します。そして、passwordsaltの両方を含むハッシュが返されます。

その後、mongooseモジュールのcreate()関数を使用して、usersモデルのルールで定義されたデータベース内にドキュメントを作成します。この関数は、メールとパスワードを含むJavascriptオブジェクトを受け取りますが、生の文字列ではなく、データベースに保存するためにsecPassパスワードのハッシュ + ソルト)を渡します。これにより、生の文字列ではなく、ハッシュとソルトを組み合わせてパスワードを安全にデータベースに保存することができます。最終的に、ユーザーモデルを含むJSONレスポンスを返します(このレスポンスの送信方法は開発フェーズのみに使用されます。本番環境では、認証トークンまたは他の方法に置き換える必要があります)。

このエンドポイントをテストするには、まずサーバーを実行する必要があります。ターミナルに以下のコマンドを入力してください。

cd <path to your project folder>
nodemon ./app.js

このコマンドにより、サーバーがlocalhostのポート3300(または指定したポート)で実行されます。その後、以下のボディを持つURL http://localhost:3300/auth/signupHTTPリクエストを送信することができます:

  1. {
  2. "email":"[email protected]",
  3. "password":"sammy"
  4. }

これにより、以下の出力/レスポンスが生成されます:

{
  "user": {
    "email": "[email protected]",
    "password": "$2a$10$JBka/WyJD0ohkzyu5Wu.JeCqQm33UIx/1xqIeNJ1AQI9kYZ0Gr0IS",
    "_id": "654510cd8f1edaa59a8bb589",
    "__v": 0
  }
}

注意:パスワードのハッシュとIDは常に一意であるため、同じではありません。

次のセクションでは、保存されたパスワードのハッシュにアクセスし、ログイン/認証時に提供されたパスワードを認証する方法について説明します。

暗号化されたパスワードへのアクセスと認証の使用

これまで、BcryptJSハッシングソルト、およびハッシュとして格納されたパスワードを持つ新しいユーザーを作成するexpressサーバーの作成方法について学びました。次に、アプリケーションにログインしようとするユーザーのパスワードを使用してユーザーを認証する方法について学びます。

認証方法を追加するには、/signupルートの後に次のルートをauth.jsに追加します:

  1. // ROUTE 2:
  2. router.post("/login", async (req, res) => {
  3. let user = await User.findOne({ email: req.body.email });
  4. if (!user) {
  5. return res.status(400).json({ error: "Login with proper credentials!" });
  6. }
  7. const passwordCompare = await bcrypt.compare(req.body.password, user.password);
  8. if (!passwordCompare) {
  9. return res
  10. .status(400)
  11. .json({ error: "Login with proper credentials!" });
  12. }
  13. res.json({ success: "Authenticated!" });
  14. });

ここでは、既存のユーザーのログイン認証を行うために/auth/loginサブパスを使用します。 /auth/signupエンドポイントと同様に、これはBcryptJSがプロミスを返すため、非同期の待機関数になります。

まず、MongooseライブラリのfindOne関数を使用して、指定された検索クエリに基づいてコレクション内のドキュメントを検索するために使用します。この場合、私たちはメールに基づいてユーザーを検索しています。指定されたメールを持つユーザーが存在しない場合、これは無効な資格情報のためのステータスコード400でレスポンスを送信します。(ログイン時にどのパラメータが間違っているかを提供することは良い慣行ではありません。攻撃者はその情報を使用して既存のアカウントを見つけることができます)。

提供されたメールアドレスのユーザーが存在する場合、プログラムはパスワードの比較に移動します。そのために、BcryptJScompare()メソッドを提供しています。このメソッドは、最初の引数として生の文字列を、2番目の引数としてhashsaltありまたはなし)を取ります。そして、パスワードがハッシュと一致する場合はtrue、一致しない場合はfalseを返します。その後、if文を使用して簡単なチェックを追加し、比較に基づいて成功またはエラーを返すことができます。

最後に、express routermodule.exportsを使用してapp.jsの起点でエクスポートし、ルートに使用します。

このルートをテストするために、以下のようなボディを持つURL http://localhost:3300/auth/login に別のHTTPレスポンスを送信することができます:

  1. {
  2. "email":"[email protected]",
  3. "password":"sammy"
  4. }

このリクエストには以下のようなレスポンスが返されます:

{
  "success": "Authenticated!"
}

最終的に、auth.jsは以下のようになります:

  1. const express = require("express");
  2. const router = express.Router();
  3. const User = require("./User");
  4. const bcrypt = require("bcryptjs");
  5. // ルート1:
  6. router.post("/signup", async (req, res) => {
  7. const salt = await bcrypt.genSalt(10);
  8. const secPass = await bcrypt.hash(req.body.password, salt);
  9. let user = await User.create({
  10. email: req.body.email,
  11. password: secPass,
  12. });
  13. res.json({ user });
  14. });
  15. // ルート2:
  16. router.post("/login", async (req, res) => {
  17. let user = await User.findOne({ email: req.body.email });
  18. if (!user) {
  19. return res.status(400).json({ error: "Login with proper credentials!" });
  20. }
  21. const passwordCompare = await bcrypt.compare(req.body.password, user.password);
  22. if (!passwordCompare) {
  23. return res
  24. .status(400)
  25. .json({ error: "Login with proper credentials!" });
  26. }
  27. res.json({ success: "Authenticated!" });
  28. });
  29. module.exports = router;

結論

このチュートリアルでは、BcryptJSを使用してデータベースのパスワードを安全に保存しアクセスする方法を説明しました。以下のような方法でさらに進めることができます:

  • ユーザーの資格情報の取得、ユーザーの資格情報の更新など、他のタスクのためのさらなるルートの実装

  • レスポンスとして送信するために、認証トークンを実装するなど。

これにより、パスワードを安全に扱うための旅が始まります。常に異なる技術や技法でより高いセキュリティを追加することができ、より安全かつ堅牢なアプリケーションを作成することができます。

Source:
https://www.digitalocean.com/community/tutorials/how-to-handle-passwords-safely-with-bcryptsjs-in-javascript