技術
約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秒単位で割った値をカウンターとして使う
- 同じ入力なら同じ出力になるので、両者で同じコードが生成される
つまり、サーバーと認証アプリが「同じシークレット」と「同じ時刻」を共有していれば、通信なしで同じコードを計算できる。
登録フロー
- ユーザーが2FA有効化をリクエスト
- サーバーがシークレットキーを生成(Base32エンコード)
- QRコードを表示(認証アプリでスキャンさせる)
- ユーザーが確認コードを入力
- 検証OKならシークレットをDBに保存
QRコードの中身は以下の形式のURI:
otpauth://totp/サービス名:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=サービス名
認証フロー
- ID/パスワード認証が通る
- 2FAが有効なユーザーには6桁コード入力を要求
- サーバー側でDBのシークレットから現在のコードを計算
- 一致すればログイン成功
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を使えばシンプル - リカバリーコードも忘れずに用意する