Mail OTP
Mail OTP(ワンタイムパスワード)は、ユーザーのメールアドレスに一時的なコードを送信するパスワードレス認証方式です。ユーザーがパスワードを覚える必要がなく、強固なセキュリティを維持できます。
概要
sequenceDiagram
participant User as ユーザー
participant Authrim
participant Email as メールサービス (SMTP)
User->>Authrim: メールアドレスを入力
Authrim->>Authrim: OTPコードを生成
Authrim->>Email: OTPメールを送信
Email->>User: OTPコードを配信
User->>Authrim: OTPコードを入力
Authrim->>Authrim: コードを検証
Authrim->>User: トークンを発行
特徴
- パスワードレス: パスワードの記憶や管理が不要
- フィッシング耐性: OTPコードは時間制限付きで使い捨て
- ユニバーサル: メールにアクセスできるすべてのデバイスで動作
- アプリ不要: TOTPと異なり、認証アプリが不要
- 馴染みのあるUX: ユーザーはメールベースの認証に慣れている
設定
環境変数
# メールサービス設定SMTP_HOST=smtp.example.comSMTP_PORT=587SMTP_PASSWORD=your-smtp-passwordSMTP_FROM_NAME=Authrim
# OTP設定OTP_LENGTH=6 # 桁数(デフォルト: 6)OTP_EXPIRY_SECONDS=300 # 有効期間(デフォルト: 5分)OTP_MAX_ATTEMPTS=3 # 最大検証試行回数OTP_RATE_LIMIT_WINDOW=3600 # レート制限ウィンドウ(秒)OTP_RATE_LIMIT_MAX=5 # ウィンドウ内の最大OTP数/メールアドレスクライアント設定
OAuthクライアントでMail OTPを有効化:
{ "client_id": "your-client-id", "allowed_grant_types": ["authorization_code", "refresh_token"], "allowed_auth_methods": ["mail_otp", "password", "passkey"], "mail_otp_config": { "enabled": true, "template": "default", "subject": "ログインコード" }}API使用方法
ステップ1: OTPリクエスト
POST /api/auth/otp/requestContent-Type: application/json
{ "client_id": "your-client-id"}レスポンス:
{ "success": true, "message": "OTPをメールに送信しました", "expires_in": 300, "otp_id": "otp_abc123..."}ステップ2: OTP検証
POST /api/auth/otp/verifyContent-Type: application/json
{ "otp_id": "otp_abc123...", "code": "123456", "client_id": "your-client-id"}成功レスポンス:
{ "success": true, "auth_code": "auth_xyz789...", "redirect_uri": "https://app.example.com/callback"}エラーレスポンス:
{ "success": false, "error": "invalid_otp", "error_description": "OTPコードが無効または期限切れです", "attempts_remaining": 2}OAuth 2.0統合
Mail OTPはOAuth 2.0認可コードフローとシームレスに統合:
// 1. mail_otpヒント付きで認可を開始const authUrl = new URL('https://auth.example.com/authorize');authUrl.searchParams.set('client_id', 'your-client-id');authUrl.searchParams.set('response_type', 'code');authUrl.searchParams.set('redirect_uri', 'https://app.example.com/callback');authUrl.searchParams.set('scope', 'openid profile email');authUrl.searchParams.set('acr_values', 'urn:authrim:acr:mail_otp'); // Mail OTPをリクエスト
window.location.href = authUrl.toString();
// 2. ユーザーがメールを受信し、AuthrimのUIでコードを入力// 3. Authrimが認可コードとともにリダイレクト// 4. コードをトークンと交換(標準OAuthフロー)セキュリティ考慮事項
OTP生成
Authrimは暗号学的に安全なOTPコードを生成:
// OTP生成(内部処理)function generateOTP(length: number = 6): string { const array = new Uint8Array(length); crypto.getRandomValues(array); return Array.from(array) .map(b => b % 10) .join('');}レート制限
悪用を防ぐため、Mail OTPは複数のレート制限を実装:
| 制限 | デフォルト | 説明 |
|---|---|---|
| メールアドレス毎 | 5/時間 | メールアドレス毎の最大OTPリクエスト数 |
| IP毎 | 20/時間 | IPアドレス毎の最大OTPリクエスト数 |
| グローバル | 1000/時間 | 全ユーザーでの最大OTPリクエスト数 |
| 検証試行 | 3回 | OTP毎の最大試行回数 |
ブルートフォース保護
// 試行追跡付き検証async function verifyOTP(otpId: string, code: string): Promise<VerifyResult> { const otp = await getOTP(otpId);
if (!otp) { throw new Error('OTPが見つからないか期限切れです'); }
if (otp.attempts >= MAX_ATTEMPTS) { await deleteOTP(otpId); throw new Error('最大試行回数を超えました'); }
if (otp.code !== code) { await incrementAttempts(otpId); return { success: false, attempts_remaining: MAX_ATTEMPTS - otp.attempts - 1 }; }
// 成功 - OTPを削除してセッションを作成 await deleteOTP(otpId); return { success: true };}ストレージ
OTPレコードはセキュリティを考慮して保存:
CREATE TABLE otp_codes ( id TEXT PRIMARY KEY, email_blind_index TEXT NOT NULL, -- 検索用ハッシュ化メール code_hash TEXT NOT NULL, -- ハッシュ化OTPコード client_id TEXT NOT NULL, attempts INTEGER DEFAULT 0, created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL, used_at INTEGER,
INDEX idx_email_blind (email_blind_index), INDEX idx_expires (expires_at));注意: OTPコードは保存前にハッシュ化されるため、データベースにアクセスしても実際のコードは分かりません。
メールテンプレート
デフォルトテンプレート
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>ログインコード</title></head><body style="font-family: sans-serif; padding: 20px;"> <h1>ログインコード</h1> <p>以下のコードでサインインしてください:</p> <div style="font-size: 32px; font-weight: bold; letter-spacing: 8px; padding: 20px; background: #f5f5f5; text-align: center;"> {{OTP_CODE}} </div> <p>このコードは{{EXPIRY_MINUTES}}分で期限切れになります。</p> <p>このコードをリクエストしていない場合は、このメールを無視してください。</p></body></html>カスタムテンプレート
クライアント毎にカスタムテンプレートを設定:
const emailTemplate = { subject: "{{APP_NAME}} - 認証コード", html: ` <div style="max-width: 600px; margin: 0 auto;"> <img src="{{LOGO_URL}}" alt="{{APP_NAME}}" width="150"> <h1>{{APP_NAME}}にサインイン</h1> <p>認証コード:</p> <code style="font-size: 24px; padding: 10px 20px; background: #e0f7fa; border-radius: 4px;"> {{OTP_CODE}} </code> <p>{{EXPIRY_MINUTES}}分間有効です。</p> </div> `, text: "{{APP_NAME}}の認証コード: {{OTP_CODE}}"};ベストプラクティス
- 適切な有効期限: 5分はセキュリティと使いやすさのバランスが良い
- レート制限の実装: 列挙攻撃とブルートフォース攻撃を防止
- OTPコードのハッシュ化: 平文のOTPコードをデータベースに保存しない
- 明確なUI: 残り時間を表示し、期限切れ後に再送信を許可
- 監査ログ: すべてのOTPリクエストと検証試行を記録
- メール配信性: 信頼できるSMTPプロバイダーを使用し、SPF/DKIM/DMARCを設定
他の方式との比較
| 方式 | セキュリティ | 使いやすさ | 必要なもの |
|---|---|---|---|
| Mail OTP | 高 | 高 | メールアクセス |
| パスワード | 中 | 中 | 記憶 |
| TOTP | 高 | 中 | 認証アプリ |
| Passkey | 非常に高 | 高 | 対応デバイス |
| SMS OTP | 中 | 高 | 電話番号 |
トラブルシューティング
よくある問題
OTPが届かない:
- 迷惑メールフォルダを確認
- SMTP設定を検証
- レート制限を確認
- メールアドレスが有効か確認
OTPが期限切れ:
- 新しいコードをリクエスト
- システム時刻の同期を確認
- 有効期限の延長を検討
最大試行回数超過:
- レート制限ウィンドウがリセットされるまで待機
- 新しいOTPコードをリクエスト
- 問題が続く場合はサポートに連絡