技術 約3分で読めます

TOTP認証(Google Authenticator等)を自社サービスに実装する

Google Authenticator や Microsoft Authenticator は Google/Microsoft 専用のアプリではない。TOTP(Time-based One-Time Password) という標準規格(RFC 6238)に対応したアプリで、自社サービスにも導入できる。

TOTPの仕組み

HMAC-SHA1(シークレットキー, floor(現在時刻 / 30)) → 6桁の数字
  • サーバーとアプリが同じシークレットキーを持つ
  • 現在時刻を30秒単位で割った値をカウンターとして使う
  • 同じ入力なら同じ出力になるので、両者で同じコードが生成される

つまり、サーバーと認証アプリが「同じシークレット」と「同じ時刻」を共有していれば、通信なしで同じコードを計算できる。

登録フロー

  1. ユーザーが2FA有効化をリクエスト
  2. サーバーがシークレットキーを生成(Base32エンコード)
  3. QRコードを表示(認証アプリでスキャンさせる)
  4. ユーザーが確認コードを入力
  5. 検証OKならシークレットをDBに保存

QRコードの中身は以下の形式のURI:

otpauth://totp/サービス名:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=サービス名

認証フロー

  1. ID/パスワード認証が通る
  2. 2FAが有効なユーザーには6桁コード入力を要求
  3. サーバー側でDBのシークレットから現在のコードを計算
  4. 一致すればログイン成功

TypeScriptでの実装例

otplib を使う。

npm install otplib qrcode

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

import { authenticator } from 'otplib';
import * as QRCode from 'qrcode';

// シークレット生成
const secret = authenticator.generateSecret();
// => "KVKFKRCPNZQUYMLXOVYDSQKJKZDTSRLD" のような文字列

// QRコード用のURI生成
const otpauth = authenticator.keyuri('user@example.com', 'MyService', secret);
// => "otpauth://totp/MyService:user@example.com?secret=KVKF...&issuer=MyService"

// QRコードを生成(Data URL)
const qrDataUrl = await QRCode.toDataURL(otpauth);
// => フロントで <img src={qrDataUrl} /> として表示

コード検証

import { authenticator } from 'otplib';

function verifyTOTP(secret: string, token: string): boolean {
  return authenticator.verify({ token, secret });
}

// 使用例
const isValid = verifyTOTP(userSecretFromDB, '123456');

otplib はデフォルトで前後1ステップ(±30秒)の誤差を許容する。時刻ずれ対策。

リカバリーコードの実装

認証アプリが使えなくなった場合のバックアップ。登録時に一度だけ表示して、ユーザーに保存させる。

import { randomBytes } from 'crypto';

function generateRecoveryCodes(count = 10): string[] {
  return Array.from({ length: count }, () =>
    randomBytes(4).toString('hex').toUpperCase()
  );
}
// => ["A1B2C3D4", "E5F6G7H8", ...] のような8文字×10個
  • DBにはハッシュ化して保存
  • 使用済みのコードは無効化(1回限り)
  • 全部使い切ったら再生成を促す

セキュリティ上の注意点

  • シークレットキーは暗号化してDBに保存
  • QRコードは一時的に表示し、キャッシュしない
  • ブルートフォース対策(試行回数制限)
  • リカバリーコードは必ずハッシュ化

まとめ

  • TOTP は標準規格なので、どの認証アプリでも使える
  • 実装は otplib を使えばシンプル
  • リカバリーコードも忘れずに用意する