見出し画像

nextjs with typescript:24 パスワードのハッシュ化(bcrypt)

JWTに慣れるため実際にちょっとしたコードをかいてみる。題材として、「/signup」や「/login」を考えてみる。その準備として「bcryptを使ったパスワードのハッシュ化」について。
 ※今回はあくまでライブラリや仕組みの理解のための練習用。実際のアプリでは何かしらの認証基盤をつかって「login」や「signup」を実装しよう。

【1】Authentication(認証) と Authorization(認可)

「/signup」や「/login」を題材にするので「Authentication(認証)」 と 「Authorization(認可)」について簡単にまとめておく。

■Authentication(認証)
・「認証」は身元を確認するプロセス
 ⇒【例】:あなたが「John Doe」であることを確認する

■Authorization(認可)
・「承認」は誰に何が許可されているかを確認するプロセス
 ⇒
【例】:Permissions(アプリケーションへの操作パーミッション等)

つまり、「/signup」や「/login」というのはAuthentication(認証)の方。

【2】/signupの実装(パスワードのDBへの格納)

まだJWTとは直接は関係ないが、まずはDBにパスワードを格納する部分を実装する。

画像1

■bcryptのインストール
パスワードの暗号化にMD5を絶対に使ってはいけない。

yarn add bcrypt
yarn add --dev @types/bcrypt

【補足:bcrypt】
※bcryptは大雑把に言うと「saltの付与(適当な文字列をパスワードに混ぜ込むこと)」と「ストレッチング(生成したハッシュ値をさらにハッシュ化することを繰り返すこと)」をしてハッシュ値をつくっている。

 bcryptの処理では「saltRounds数」というのがあって、これは『2の「saltRounds」乗』だけ処理を繰り返してハッシュ化するというもの。
 この数が大きすぎると処理に時間がかかりすぎて問題が起きることもある。
 例えば、ユーザ登録のためのアクセスが大量発生した場合、bcryptによるパスワードハッシュ化処理に時間がかかってしまい、ハッシュ値の生成が追い付かない、という事態となりかねない。
 強固にしたいからといって単純に「saltRounds数」を大きくすればいいわけでもない。

もっと詳しいことは世界中にいるセキュリティの専門家へ聞こう。

■サンプル用のDBマイグレーション設定
作業用にマイグレーション設定を準備。今回はsqliteを使うのでモジュールをいれておく。

yarn add sqlite
yarn add sqlite3
yarn add --dev @types/sqlite3

【./migrations/001-initial.sql】

-- Up
CREATE TABLE Person (
   id INTEGER PRIMARY KEY AUTOINCREMENT,
   name TEXT,
   email TEXT,
   password TEXT
);


-- Down
DROP TABLE Person;

↓ マイグレーションを実行するスクリプトを作成する

【./database-test.js】

const sqlite = require('sqlite');
const sqlite3= require('sqlite3');


async function setup() {
  const db = await sqlite.open({filename:'./mydb.sqlite',driver: sqlite3.Database});
  await db.migrate({force: true});

  const people = await db.all('SELECT * FROM person');
  console.log('ALL PEOPLE', JSON.stringify(people, null, 2));

}

setup();

■postメソッドを受けてDBへinsertする(bcrypt未実施)
 postメソッドを受けてDBへinsertする
 ⇒ 「/api」配下に書いてメソッドによる分岐処理をする。

【./pages/api/signup.ts】

import { NextApiRequest, NextApiResponse } from 'next';
import sqlite3 from 'sqlite3';
import {open} from 'sqlite';


export default async function signup(req:NextApiRequest,res:NextApiResponse){

   //DBを開く
   const db = await open(
       {
           filename:'./mydb.sqlite',
           driver: sqlite3.Database
       }
   );

   //POSTをうける
   if(req.method ==='POST'){

       //insert文を発行
       const statement = await db.prepare(
           'INSERT INTO Person (name, email, password) values (?,?,?)'
       );

       const result = await statement.run(
           req.body.name,
           req.body.email,
           req.body.password
       );

       //デバッグ用に出力(実際はセキュリティのためpasswordカラムはとらない)
       const person = await db.all('select * from person');
       res.json(person);

   }else {
       res.status(405).json(
           {message:'We Only Support POST method!'}
       );
   }


}

・GETメソッドを投げてみる

画像2

・POSTメソッドを投げてみる(Postmanで実行する)

画像3

画像4

画像5

やりたいことは、password部分の暗号化(ハッシュ化)なので、次にbcryptでハッシュ化する。

【3】bcryptでのパスワードハッシュ化とDBへのinsert

パスワードのハッシュ化については以下に記述がある

ここでは「Technique 2 (auto-gen a salt and hash)」側を使う。

【使い方】

import {hash} from 'bcrypt';

...(略)...

bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
   // myPlaintextPasswordがハッシュ化(ラウンド数はsaltRounds回)されて変数名hashに入っている
   
   // この中にinsert分などをいれる
});

「bcrypt.hash()」の中でhash化したパスワードに何かしらの処理(function部分で記述)をするような感じになる。


【./pages/api/signup.ts】
今回のsaltRoundsは「10」を設定。

import { NextApiRequest, NextApiResponse } from 'next';
import sqlite3 from 'sqlite3';
import {open} from 'sqlite';
import {hash} from 'bcrypt';


export default async function signup(req:NextApiRequest,res:NextApiResponse){

   //DBを開く
   const db = await open(
       {
           filename:'./mydb.sqlite',
           driver: sqlite3.Database
       }
   );

   //POSTをうける
   if(req.method ==='POST'){

       //bcryptのhashで
       hash(req.body.password,
           10, 
           async function(err,hash){
               //insert文を発行
               const statement = await db.prepare(
                   'INSERT INTO Person (name, email, password) values (?,?,?)'
               );

               const result = await statement.run(
                   req.body.name,
                   req.body.email,
                   hash  //ハッシュ化された値を格納
               );


               //デバッグ用に出力(実際はセキュリティのためpasswordカラムはとらない)
               const person = await db.all('select * from person');
               res.json(person);
           }  
       );
   }else {
       res.status(405).json(
           {message:'We Only Support POST method!'}
       );
   }


}

画像6

画像7

パスワードのハッシュ化とDBへの格納まで実装してみた。

次回は「/login」相当として、格納したパスワードとログインリクエストのパスワードを比較したり、認証できたらJWTを返す、といったことをプログラムしてみる。

もっと応援したいなと思っていただけた場合、よろしければサポートをおねがいします。いただいたサポートは活動費に使わせていただきます。