セキュリティ レッスン3
認証とセキュリティ
パスワード管理、JWT、OAuth 2.0、多要素認証を学ぼう
パスワードの安全な管理
パスワードは絶対に平文で保存してはいけません。 データベースが漏洩した場合、全ユーザーのパスワードが即座に流出します。bcrypt などのハッシュ関数を使ってハッシュ化して保存します。
// 危険: 平文で保存
// INSERT INTO users (email, password) VALUES ('user@example.com', 'mypassword123')
// 安全: bcrypt でハッシュ化
import bcrypt from "bcrypt";
// ユーザー登録時:パスワードをハッシュ化して保存
async function register(email, password) {
const saltRounds = 12; // ストレッチング回数
const hashedPassword = await bcrypt.hash(password, saltRounds);
// hashedPassword → "$2b$12$LJ3m4ys..." のような文字列
await db.user.create({
data: { email, password: hashedPassword },
});
}
// ログイン時:入力パスワードとハッシュを比較
async function login(email, password) {
const user = await db.user.findUnique({ where: { email } });
if (!user) return null;
const isValid = await bcrypt.compare(password, user.password);
return isValid ? user : null;
}bcrypt は内部でソルト(ランダムな文字列)を自動追加するため、 同じパスワードでも異なるハッシュ値が生成されます。
JWT(JSON Web Token)
JWT はステートレスな認証に使われるトークンです。 サーバーにセッション状態を保持せず、トークン自体にユーザー情報を含みます。 Header、Payload、Signature の3つの部分で構成されます。
// JWTの構造
// eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.xxxxx
// |--- Header ---|.|----- Payload ------|.|- Signature -|
// Header: { "alg": "HS256", "typ": "JWT" }
// Payload: { "userId": 1, "email": "user@example.com", "exp": 1700000000 }
// Signature: HMAC-SHA256(header + payload, secret)import jwt from "jsonwebtoken";
const SECRET = process.env.JWT_SECRET; // 環境変数で管理
// トークン生成(ログイン成功時)
function generateToken(user) {
return jwt.sign(
{ userId: user.id, email: user.email },
SECRET,
{ expiresIn: "1h" } // 有効期限1時間
);
}
// トークン検証(APIリクエスト時)
function verifyToken(token) {
try {
return jwt.verify(token, SECRET);
} catch (err) {
return null; // 無効または期限切れ
}
}
// ミドルウェアで保護されたルートを守る
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.replace("Bearer ", "");
const decoded = verifyToken(token);
if (!decoded) {
return res.status(401).json({ error: "認証が必要です" });
}
req.user = decoded;
next();
}OAuth 2.0
OAuth 2.0 は、サードパーティアプリケーションに ユーザーのリソースへのアクセスを安全に委任するための認可フレームワークです。 「Googleでログイン」「GitHubでログイン」などに使われています。
OAuth 2.0 認可コードフローの流れ:
1. ユーザーが「Googleでログイン」をクリック
→ アプリがGoogleの認可エンドポイントにリダイレクト
2. ユーザーがGoogleでログインし、アクセスを許可
→ Googleが認可コードをアプリにリダイレクト
3. アプリが認可コードをGoogleのトークンエンドポイントに送信
→ アクセストークンを取得
4. アプリがアクセストークンでGoogle APIにアクセス
→ ユーザー情報(名前、メール等)を取得// Next.js での OAuth 実装例(NextAuth.js)
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
callbacks: {
async session({ session, token }) {
session.user.id = token.sub;
return session;
},
},
});
export { handler as GET, handler as POST };多要素認証(MFA)
多要素認証(Multi-Factor Authentication)は、 2つ以上の認証要素を組み合わせてセキュリティを強化する方法です。
知識要素
パスワード、PIN、セキュリティの質問
所有要素
スマートフォン、セキュリティキー、ICカード
生体要素
指紋、顔認証、虹彩認証
// TOTP(Time-based One-Time Password)の仕組み
// Google Authenticator 等で使われる
// 1. ユーザーがMFAを有効にする際に秘密鍵を共有
// 2. アプリとサーバーが同じアルゴリズムで6桁コードを生成
// 3. 30秒ごとにコードが変わる
import { authenticator } from "otplib";
// 秘密鍵の生成(MFA設定時)
const secret = authenticator.generateSecret();
// → QRコードとしてユーザーに表示
// コードの検証(ログイン時)
function verifyTOTP(token, userSecret) {
return authenticator.verify({ token, secret: userSecret });
}
// 使い方
const isValid = verifyTOTP("123456", userSecret);セッション管理のベストプラクティス
- HttpOnly Cookie:JavaScriptからCookieにアクセスできないようにする
- Secure フラグ:HTTPS 接続でのみCookieを送信する
- SameSite 属性:クロスサイトリクエストでのCookie送信を制限する
- 有効期限:セッションに適切な有効期限を設定する
- ログアウト時の無効化:セッションIDを確実に破棄する
// 安全なCookie設定
res.cookie("session", sessionId, {
httpOnly: true, // JSからアクセス不可
secure: true, // HTTPSのみ
sameSite: "strict", // クロスサイト送信禁止
maxAge: 3600000, // 1時間
path: "/",
});まとめ
- パスワードは bcrypt でハッシュ化して保存する(平文保存は厳禁)
- JWT はステートレスな認証に使えるが、有効期限とシークレット管理が重要
- OAuth 2.0 はサードパーティログインの標準的な仕組み
- 多要素認証(MFA)で知識・所有・生体の複数要素を組み合わせる
- Cookie は HttpOnly, Secure, SameSite を適切に設定する