Use Cases

Implementing Two-Factor Authentication (2FA) with Claude Code

Learn about implementing two-factor authentication (2fa) using Claude Code. Includes practical code examples.

二要素認証をClaude Codeで実装する

アカウントのセキュリティを強化する二要素認証(2FA)は、現在のWebアプリケーションに欠かせない機能です。Claude Codeを使えば、TOTPベースの2FA、QRコード表示、バックアップコードを含む完全な実装を構築できます。

TOTPの仕組み

TOTP(Time-based One-Time Password)は、共有シークレットと現在時刻から30秒ごとに6桁のコードを生成するアルゴリズムです。Google AuthenticatorやAuthyなどのアプリで利用されています。

> TOTPベースの二要素認証を実装して。
> QRコード生成、有効化フロー、ログイン時の検証、
> バックアップコード10個の生成も含めて。

シークレット生成とQRコード

// src/lib/two-factor.ts
import { authenticator } from 'otplib';
import QRCode from 'qrcode';
import crypto from 'crypto';
import { prisma } from './prisma';

export class TwoFactorService {
  // 2FA設定の開始(シークレットとQRコード生成)
  async setup(userId: string) {
    const secret = authenticator.generateSecret();
    const user = await prisma.user.findUnique({ where: { id: userId } });

    const otpauthUrl = authenticator.keyuri(
      user!.email,
      'MyApp',
      secret
    );

    const qrCodeDataUrl = await QRCode.toDataURL(otpauthUrl);

    // シークレットを一時保存(まだ有効化しない)
    await prisma.user.update({
      where: { id: userId },
      data: { twoFactorSecret: secret, twoFactorEnabled: false },
    });

    return { qrCodeDataUrl, secret };
  }

  // コードを検証して2FAを有効化
  async enable(userId: string, token: string) {
    const user = await prisma.user.findUnique({ where: { id: userId } });
    if (!user?.twoFactorSecret) throw new Error('2FAが設定されていません');

    const isValid = authenticator.verify({
      token,
      secret: user.twoFactorSecret,
    });

    if (!isValid) throw new Error('無効なコードです');

    // バックアップコードを生成
    const backupCodes = this.generateBackupCodes();
    const hashedCodes = backupCodes.map((code) =>
      crypto.createHash('sha256').update(code).digest('hex')
    );

    await prisma.user.update({
      where: { id: userId },
      data: {
        twoFactorEnabled: true,
        backupCodes: hashedCodes,
      },
    });

    return { backupCodes }; // ユーザーに表示(一度だけ)
  }

  // ログイン時のコード検証
  async verify(userId: string, token: string): Promise<boolean> {
    const user = await prisma.user.findUnique({ where: { id: userId } });
    if (!user?.twoFactorSecret) return false;

    // TOTPコードで検証
    const isValid = authenticator.verify({
      token,
      secret: user.twoFactorSecret,
    });

    if (isValid) return true;

    // バックアップコードで検証
    const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
    const codeIndex = user.backupCodes.indexOf(hashedToken);
    if (codeIndex !== -1) {
      // 使用済みのバックアップコードをDelete
      const updatedCodes = [...user.backupCodes];
      updatedCodes.splice(codeIndex, 1);
      await prisma.user.update({
        where: { id: userId },
        data: { backupCodes: updatedCodes },
      });
      return true;
    }

    return false;
  }

  private generateBackupCodes(count = 10): string[] {
    return Array.from({ length: count }, () =>
      crypto.randomBytes(4).toString('hex').toUpperCase()
    );
  }
}

2FA設定画面

// src/components/TwoFactorSetup.tsx
'use client';
import { useState } from 'react';

export function TwoFactorSetup() {
  const [step, setStep] = useState<'idle' | 'qr' | 'verify' | 'backup'>('idle');
  const [qrCode, setQrCode] = useState('');
  const [token, setToken] = useState('');
  const [backupCodes, setBackupCodes] = useState<string[]>([]);

  const startSetup = async () => {
    const res = await fetch('/api/user/2fa/setup', { method: 'POST' });
    const data = await res.json();
    setQrCode(data.qrCodeDataUrl);
    setStep('qr');
  };

  const verifyAndEnable = async () => {
    const res = await fetch('/api/user/2fa/enable', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token }),
    });
    const data = await res.json();
    setBackupCodes(data.backupCodes);
    setStep('backup');
  };

  if (step === 'idle') {
    return (
      <button onClick={startSetup} className="bg-blue-600 text-white px-6 py-2 rounded-lg">
        二要素認証を設定する
      </button>
    );
  }

  if (step === 'qr') {
    return (
      <div className="space-y-4">
        <p className="dark:text-gray-300">認証アプリでQRコードをスキャンしてください</p>
        <img src={qrCode} alt="QRコード" className="mx-auto" />
        <input
          value={token}
          onChange={(e) => setToken(e.target.value)}
          placeholder="6桁のコードを入力"
          className="w-full border rounded px-3 py-2"
          maxLength={6}
        />
        <button onClick={verifyAndEnable} className="bg-blue-600 text-white px-6 py-2 rounded-lg w-full">
          確認して有効化
        </button>
      </div>
    );
  }

  return (
    <div className="space-y-4">
      <h3 className="font-bold text-green-600">二要素認証が有効になりました</h3>
      <p className="text-sm text-gray-600 dark:text-gray-400">
        以下のバックアップコードを安全な場所に保存してください。各コードは一度だけ使えます。
      </p>
      <div className="grid grid-cols-2 gap-2 bg-gray-50 dark:bg-gray-800 p-4 rounded-lg font-mono text-sm">
        {backupCodes.map((code, i) => (
          <span key={i} className="dark:text-gray-300">{code}</span>
        ))}
      </div>
    </div>
  );
}

関連記事

認証全般については認証機能の実装ガイド、パスワード関連はパスワードリセット実装も参考にしてください。

TOTPの仕様はRFC 6238で定義されています。

#Claude Code #two-factor auth #2FA #TOTP #security