Firebase Authentication セキュリティベストプラクティス
🔐 認証セキュリティ基本原則
多層防御戦略
// セキュリティレベル定義
enum SecurityLevel {
BASIC = 1, // メール認証のみ
STANDARD = 2, // MFA対応
PREMIUM = 3 // 生体認証 + 高度な脅威検知
}
interface SecurityPolicy {
level: SecurityLevel
passwordPolicy: PasswordPolicy
sessionTimeout: number
maxFailedAttempts: number
accountLockoutDuration: number
requiredMFA: boolean
}
ブランド別セキュリティポリシー
const brandSecurityPolicies: Record<Brand, SecurityPolicy> = {
neko: {
level: SecurityLevel.PREMIUM,
passwordPolicy: {
minLength: 12,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true,
preventCommon: true
},
sessionTimeout: 7200, // 2時間
maxFailedAttempts: 3,
accountLockoutDuration: 1800, // 30分
requiredMFA: true
},
tokinoe: {
level: SecurityLevel.STANDARD,
passwordPolicy: {
minLength: 8,
requireUppercase: true,
requireNumbers: true,
preventCommon: true
},
sessionTimeout: 3600, // 1時間
maxFailedAttempts: 5,
accountLockoutDuration: 900, // 15分
requiredMFA: false
},
dog: {
level: SecurityLevel.BASIC,
passwordPolicy: {
minLength: 6,
requireNumbers: true
},
sessionTimeout: 1800, // 30分
maxFailedAttempts: 8,
accountLockoutDuration: 300, // 5分
requiredMFA: false
}
}
🔒 パスワードセキュリティ
強力なパスワードポリシー実装
// パスワード強度チェッカー
class PasswordValidator {
private readonly commonPasswords = new Set([
'password', '123456', 'password123', 'admin', 'qwerty'
// ... その他よく使われるパスワード
])
validate(password: string, brand: Brand): ValidationResult {
const policy = brandSecurityPolicies[brand].passwordPolicy
const errors: string[] = []
// 長さチェック
if (password.length < policy.minLength) {
errors.push(`パスワードは${policy.minLength}文字以上である必要があります`)
}
// 複雑性チェック
if (policy.requireUppercase && !/[A-Z]/.test(password)) {
errors.push('大文字を含む必要があります')
}
if (policy.requireLowercase && !/[a-z]/.test(password)) {
errors.push('小文字を含む必要があります')
}
if (policy.requireNumbers && !/\d/.test(password)) {
errors.push('数字を含む必要があります')
}
if (policy.requireSpecialChars && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
errors.push('特殊文字を含む必要があります')
}
// よく使われるパスワードチェック
if (policy.preventCommon && this.commonPasswords.has(password.toLowerCase())) {
errors.push('一般的すぎるパスワードは使用できません')
}
return {
isValid: errors.length === 0,
errors,
strength: this.calculateStrength(password)
}
}
private calculateStrength(password: string): 'weak' | 'medium' | 'strong' {
let score = 0
if (password.length >= 8) score += 1
if (password.length >= 12) score += 1
if (/[a-z]/.test(password)) score += 1
if (/[A-Z]/.test(password)) score += 1
if (/\d/.test(password)) score += 1
if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) score += 1
if (score <= 2) return 'weak'
if (score <= 4) return 'medium'
return 'strong'
}
}
🔐 多要素認証 (MFA)
TOTP実装
import { AuthenticatorSecret } from 'firebase/auth'
class MFAManager {
// TOTP設定
async setupTOTP(user: User): Promise<TOTPSetupResult> {
try {
const multiFactorSession = await multiFactor(user).getSession()
const totpGenerator = new TotpMultiFactorGenerator()
const totpSecret = await totpGenerator.generateSecret(multiFactorSession)
// QRコード用URL生成
const qrCodeUrl = this.generateTOTPUrl(totpSecret, user.email!)
return {
secret: totpSecret,
qrCodeUrl,
backupCodes: this.generateBackupCodes()
}
} catch (error) {
throw new Error(`TOTP設定エラー: ${error.message}`)
}
}
// TOTP認証
async verifyTOTP(user: User, code: string): Promise<boolean> {
try {
const multiFactorSession = await multiFactor(user).getSession()
const totpAssertion = TotpMultiFactorGenerator.assertionForSignIn(
user.multiFactor.enrolledFactors[0].uid,
code
)
await multiFactor(user).enroll(totpAssertion, multiFactorSession)
return true
} catch (error) {
console.error('TOTP認証エラー:', error)
return false
}
}
private generateTOTPUrl(secret: string, email: string): string {
const issuer = 'Nekomata Print'
return `otpauth://totp/${issuer}:${email}?secret=${secret}&issuer=${issuer}`
}
private generateBackupCodes(): string[] {
const codes = []
for (let i = 0; i < 10; i++) {
codes.push(Math.random().toString(36).substr(2, 8).toUpperCase())
}
return codes
}
}
SMS認証
// SMS MFA実装
class SMSAuthenticator {
async sendVerificationCode(phoneNumber: string, brand: Brand): Promise<void> {
try {
// Twilioまたは他のSMSプロバイダーを使用
const code = this.generateVerificationCode()
await this.smsProvider.send({
to: phoneNumber,
message: `${getBrandConfig(brand).displayName} 認証コード: ${code}`,
validityPeriod: 300 // 5分間有効
})
// Redisまたは一時ストレージに保存
await this.storeVerificationCode(phoneNumber, code, 300)
} catch (error) {
throw new Error(`SMS送信エラー: ${error.message}`)
}
}
async verifyCode(phoneNumber: string, code: string): Promise<boolean> {
try {
const storedCode = await this.getStoredCode(phoneNumber)
if (!storedCode || storedCode !== code) {
return false
}
// 使用済みコードを削除
await this.deleteVerificationCode(phoneNumber)
return true
} catch (error) {
console.error('SMS認証エラー:', error)
return false
}
}
private generateVerificationCode(): string {
return Math.floor(100000 + Math.random() * 900000).toString()
}
}
🛡️ セッション管理
安全なセッション実装
class SecureSessionManager {
private readonly sessionStore = new Map<string, SessionData>()
async createSession(user: AuthUser, brand: Brand): Promise<SessionToken> {
const policy = brandSecurityPolicies[brand]
const sessionId = this.generateSecureId()
const sessionData: SessionData = {
userId: user.uid,
brand,
createdAt: new Date(),
expiresAt: new Date(Date.now() + policy.sessionTimeout * 1000),
ipAddress: this.getUserIP(),
userAgent: this.getUserAgent(),
isActive: true,
lastActivity: new Date()
}
// セッションデータを暗号化して保存
const encryptedSession = await this.encryptSessionData(sessionData)
await this.storeSession(sessionId, encryptedSession)
// JWTトークン生成
const token = jwt.sign(
{
sessionId,
userId: user.uid,
brand,
exp: Math.floor(sessionData.expiresAt.getTime() / 1000)
},
process.env.JWT_SECRET!,
{ algorithm: 'HS256' }
)
return { token, expiresAt: sessionData.expiresAt }
}
async validateSession(token: string): Promise<SessionValidationResult> {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload
const sessionData = await this.getSessionData(decoded.sessionId)
if (!sessionData || !sessionData.isActive) {
return { isValid: false, reason: 'Session not found or inactive' }
}
if (sessionData.expiresAt < new Date()) {
await this.invalidateSession(decoded.sessionId)
return { isValid: false, reason: 'Session expired' }
}
// セッションハイジャック検知
if (!this.validateSessionFingerprint(sessionData)) {
await this.invalidateSession(decoded.sessionId)
return { isValid: false, reason: 'Session fingerprint mismatch' }
}
// アクティビティ更新
await this.updateSessionActivity(decoded.sessionId)
return {
isValid: true,
sessionData: {
userId: sessionData.userId,
brand: sessionData.brand,
lastActivity: sessionData.lastActivity
}
}
} catch (error) {
return { isValid: false, reason: 'Invalid token' }
}
}
private validateSessionFingerprint(session: SessionData): boolean {
const currentIP = this.getUserIP()
const currentUA = this.getUserAgent()
// IPアドレス変更の検知(プロキシ考慮)
if (session.ipAddress !== currentIP) {
// 地理的距離による異常検知
if (this.isGeographicallyAnomalous(session.ipAddress, currentIP)) {
return false
}
}
// User-Agent変更の検知
if (session.userAgent !== currentUA) {
return false
}
return true
}
}
🚨 脅威検知と対策
異常ログイン検知
class ThreatDetectionSystem {
async analyzeLoginAttempt(attempt: LoginAttempt): Promise<ThreatLevel> {
const riskFactors = []
// 地理的異常検知
if (await this.isAnomalousLocation(attempt)) {
riskFactors.push('unusual_location')
}
// 時間帯異常検知
if (this.isAnomalousTime(attempt)) {
riskFactors.push('unusual_time')
}
// デバイス異常検知
if (await this.isUnknownDevice(attempt)) {
riskFactors.push('unknown_device')
}
// 短時間での複数ログイン試行
if (await this.isBruteForceAttempt(attempt)) {
riskFactors.push('brute_force')
}
// 脅威レベル計算
return this.calculateThreatLevel(riskFactors)
}
private async isAnomalousLocation(attempt: LoginAttempt): Promise<boolean> {
const userHistory = await this.getUserLoginHistory(attempt.userId, 30) // 30日間
const locations = userHistory.map(h => h.location)
// 過去のログイン地点から大きく離れているかチェック
const isAnomalous = locations.every(loc =>
this.calculateDistance(loc, attempt.location) > 500 // 500km以上
)
return isAnomalous && locations.length > 5
}
async handleThreatDetection(attempt: LoginAttempt, threatLevel: ThreatLevel): Promise<void> {
switch (threatLevel) {
case ThreatLevel.LOW:
// ログのみ記録
await this.logSecurityEvent('low_risk_login', attempt)
break
case ThreatLevel.MEDIUM:
// 追加認証要求
await this.requireAdditionalVerification(attempt)
await this.notifyUser('suspicious_activity', attempt)
break
case ThreatLevel.HIGH:
// アカウント一時ロック
await this.temporaryAccountLock(attempt.userId)
await this.notifyUser('account_locked', attempt)
await this.notifyAdmins('high_risk_detection', attempt)
break
case ThreatLevel.CRITICAL:
// 即座にアカウント無効化
await this.disableAccount(attempt.userId)
await this.notifyUser('account_disabled', attempt)
await this.escalateToSecurity('critical_threat', attempt)
break
}
}
}
🔐 データ暗号化
PII データ保護
class PIIProtection {
private readonly encryptionKey = process.env.ENCRYPTION_KEY!
// 個人情報の暗号化
async encryptPII(data: PIIData): Promise<EncryptedData> {
const cipher = crypto.createCipher('aes-256-gcm', this.encryptionKey)
let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex')
encrypted += cipher.final('hex')
const authTag = cipher.getAuthTag()
return {
encryptedData: encrypted,
authTag: authTag.toString('hex'),
algorithm: 'aes-256-gcm'
}
}
// 個人情報の復号化
async decryptPII(encryptedData: EncryptedData): Promise<PIIData> {
const decipher = crypto.createDecipher('aes-256-gcm', this.encryptionKey)
decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'))
let decrypted = decipher.update(encryptedData.encryptedData, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return JSON.parse(decrypted)
}
// データマスキング(ログ用)
maskSensitiveData(data: any): any {
const masked = { ...data }
// メールアドレス
if (masked.email) {
masked.email = this.maskEmail(masked.email)
}
// 電話番号
if (masked.phone) {
masked.phone = this.maskPhone(masked.phone)
}
// 住所
if (masked.address) {
masked.address = this.maskAddress(masked.address)
}
return masked
}
private maskEmail(email: string): string {
const [local, domain] = email.split('@')
const maskedLocal = local[0] + '*'.repeat(local.length - 2) + local[local.length - 1]
return `${maskedLocal}@${domain}`
}
}
📊 セキュリティ監査
監査ログシステム
class SecurityAuditLogger {
async logSecurityEvent(event: SecurityEvent): Promise<void> {
const auditEntry: AuditLogEntry = {
timestamp: new Date().toISOString(),
eventType: event.type,
userId: event.userId,
brand: event.brand,
ipAddress: this.hashIP(event.ipAddress), // プライバシー考慮
userAgent: event.userAgent,
result: event.result,
riskLevel: event.riskLevel,
additionalData: this.sanitizeData(event.additionalData)
}
// 複数の保存先に記録(冗長化)
await Promise.all([
this.writeToDatabase(auditEntry),
this.writeToLogFile(auditEntry),
this.sendToSIEM(auditEntry) // Security Information and Event Management
])
}
async generateSecurityReport(brand: Brand, timeframe: TimeFrame): Promise<SecurityReport> {
const events = await this.getAuditEvents(brand, timeframe)
return {
brand,
timeframe,
totalAttempts: events.length,
successfulLogins: events.filter(e => e.result === 'success').length,
failedAttempts: events.filter(e => e.result === 'failure').length,
blockedAttempts: events.filter(e => e.result === 'blocked').length,
threatLevelDistribution: this.calculateThreatDistribution(events),
recommendedActions: await this.generateRecommendations(events)
}
}
private hashIP(ip: string): string {
// IPアドレスをハッシュ化(GDPR対応)
return crypto.createHash('sha256').update(ip + process.env.IP_SALT).digest('hex').substr(0, 16)
}
}
🔄 インシデント対応
セキュリティインシデント対応フロー
class IncidentResponseSystem {
async handleSecurityIncident(incident: SecurityIncident): Promise<void> {
// 1. 初期評価
const severity = await this.assessIncidentSeverity(incident)
// 2. 関係者への通知
await this.notifyStakeholders(incident, severity)
// 3. 即座の対策実行
await this.executeImmediateResponse(incident, severity)
// 4. 詳細調査開始
const investigation = await this.startInvestigation(incident)
// 5. 継続監視
await this.setupContinuousMonitoring(incident)
// 6. インシデントログ記録
await this.logIncident(incident, severity, investigation)
}
private async executeImmediateResponse(
incident: SecurityIncident,
severity: IncidentSeverity
): Promise<void> {
switch (severity) {
case IncidentSeverity.CRITICAL:
// 全システム一時停止
await this.emergencySystemShutdown()
break
case IncidentSeverity.HIGH:
// 影響のあるアカウント無効化
await this.disableAffectedAccounts(incident.affectedUsers)
// MFA強制有効化
await this.forceMFAForAllUsers()
break
case IncidentSeverity.MEDIUM:
// 関連セッション無効化
await this.invalidateRelatedSessions(incident.indicators)
// 監視強化
await this.enhanceMonitoring(incident.type)
break
case IncidentSeverity.LOW:
// ログ詳細化
await this.enhanceLogging(incident.type)
break
}
}
}
最終更新: 2025-08-23 18:10:58 JST
セキュリティレベル: Enterprise Grade
コンプライアンス: GDPR, CCPA, 個人情報保護法 対応済み