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にパスワードを格納する部分を実装する。
■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メソッドを投げてみる
・POSTメソッドを投げてみる(Postmanで実行する)
やりたいことは、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!'}
);
}
}
パスワードのハッシュ化とDBへの格納まで実装してみた。
次回は「/login」相当として、格納したパスワードとログインリクエストのパスワードを比較したり、認証できたらJWTを返す、といったことをプログラムしてみる。
もっと応援したいなと思っていただけた場合、よろしければサポートをおねがいします。いただいたサポートは活動費に使わせていただきます。