Node.jsを使ったシステム開発において、パスワード認証は非常に重要な機能の一つです。
本記事では、クライアントから送信されたパスワードをデータベースのデータと照合する認証処理を実装する方法について解説します。
目次
実装の背景
システムでは、以下のセキュリティ要件を満たす必要があります。
- データベース内のパスワードは暗号化された状態で保存する
- クライアント側では、パスワードをMD5ハッシュ化して送信する
- サーバー側で復号化したデータとクライアントからのハッシュ値を比較する
このような要件に対応するために、Node.jsのcrypto
モジュールを活用します。
データ登録処理
以下は、ユーザーデータを登録する処理の例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
const crypto = require('crypto'); const { getCipheredData } = require('./common/crypto'); /** * データ新規登録 */ const insertUser = async (client, req) => { try { const userPasswordMd5 = req.body.password; // パスワードを暗号化 const password = getCipheredData(userPasswordMd5, req.config.crypto.password, req.config.crypto.user.salt, Buffer.from(req.config.crypto.user.iv)); // ユーザー登録 const insertUser = { text: ` INSERT INTO user( user_id, password ) VALUES($1, $2) RETURNING id`, values: [ req.body.user_id, password ], }; const result = await client.query(insertUser); return result.rows[0].id; } catch (err) { throw err; } }; |
パスワード認証処理
以下に、認証処理を簡潔にまとめたコードを示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
const jwt = require("jsonwebtoken"); const { getDecipheredData, encryptMd5 } = require("../../common/crypto"); /** * ログイン処理関数 */ const signIn = async (client, req) => { try { const userPasswordMd5 = req.body.password; // データベースからユーザー情報を取得 const query = { text: ` SELECT id, user_id, password FROM user WHERE user_id = $1 AND del_flg = FALSE; `, values: [ req.body.user_id ], }; const result = await client.query(query); // 0件 if (result.rows.length == 0) { return null; }; const password = result.rows[0].password; // パスワードを復号化 const decryptedPassword = getDecipheredData( password, req.config.crypto.password, req.config.crypto.user.salt, Buffer.from(req.config.crypto.user.iv) ); // MD5ハッシュ化 const passwordMd5 = encryptMd5(decryptedPassword); // クライアントからのMD5パスワードと一致するか確認 if (userPasswordMd5 === passwordMd5) { // 一致したユーザーの情報を使ってトークンを発行 const payload = { userId: req.body.user_id, password: req.body.password, }; const accessToken = jwt.sign(payload, req.config.api_token.secret_key, { expiresIn: '90d' }); const refreshToken = jwt.sign(payload, req.config.api_token.secret_key, { expiresIn: '91d' }); return { userId: req.body.user_id, accessToken, refreshToken, }; } // 一致するデータがない場合 return null; } catch (err) { throw err; } }; module.exports = { signIn }; |
暗号化・復号化処理
以下は、暗号化および復号化を行うためのユーティリティ関数を定義したcrypto.js
ファイルの内容です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
const crypto = require('crypto'); const algorithm = 'aes-256-cbc'; // AESアルゴリズム[256ビット, CBCモード:Cipher Block Chaining mode (暗号ブロック連鎖モード)] /** * 暗号化処理 * * @param {*} data 平文文字列 * @param {*} password パスワード * @param {*} salt ソルト * @param {*} iv 初期化ベクトル * @return {*} */ const getCipheredData = (data, password, salt, iv) => { const inputEncoding = 'utf8'; const outputEncoding = 'hex'; // hexadecimal(16進数) const key = crypto.scryptSync(password, salt, 32); const cipher = crypto.createCipheriv(algorithm, key, iv); // 暗号化用インスタンス // 暗号化 let cipheredData = cipher.update(data, inputEncoding, outputEncoding); cipheredData += cipher.final(outputEncoding); return cipheredData; }; /** * 復号処理 * * @param {*} data 暗号化文字列 * @param {*} password パスワード * @param {*} salt ソルト * @param {*} iv 初期化ベクトル * @return {*} */ const getDecipheredData = (data, password, salt, iv) => { const inputEncoding = 'utf8'; const outputEncoding = 'hex'; // hexadecimal(16進数) const key = crypto.scryptSync(password, salt, 32); const decipher = crypto.createDecipheriv(algorithm, key, iv); // 復号用インスタンス // 復号 let decipheredData = decipher.update(data, outputEncoding, inputEncoding); decipheredData += decipher.final(inputEncoding); return decipheredData; }; /** * sha256ハッシュ化処理 * * @param {*} data 平文文字列 * @return {*} */ const encryptSha256 = (data) => { const hash = crypto.createHash('sha256'); hash.update(data); return hash.digest('hex'); }; module.exports = { getCipheredData, getDecipheredData, encryptSha256 }; |
処理の流れ
STEP
クライアントからのリクエスト受信
クライアントから送信されたMD5ハッシュ化済みパスワードを受け取ります。
STEP
データベースからユーザー情報を取得
全ユーザーのパスワードを含む情報をデータベースから取得します。
STEP
パスワードの復号化と照合
- データベースに保存されている暗号化パスワードを復号化します。
- 復号化したパスワードをMD5ハッシュ化して、クライアントからの値と比較します。
STEP
認証成功時の処理
一致した場合は、アクセストークンとリフレッシュトークンを発行します。
STEP
認証失敗時の処理
一致するデータがない場合はnull
を返します。
注意点
暗号化アルゴリズムやハッシュ化アルゴリズムを選定する際は、セキュリティ要件に基づいて慎重に検討してください。
MD5は衝突耐性が低いため、場合によってはSHA-256やbcryptを検討すべきです。
データベースのクエリで不要なデータを取得しないようにすることで、処理の効率を向上させることができます。
まとめ
本記事では、Node.jsを用いたパスワード認証処理の実装例を解説しました。
このコードをベースに、セキュリティ要件に応じた拡張やカスタマイズを行い、より安全な認証機能を実現してください。
コメント