Firebase Auth
Library
Firebase Auth
Overview
Firebase Auth is a comprehensive authentication service provided by Google, optimized for mobile and web applications. Offers a generous free tier with up to 50,000 MAU (Monthly Active Users), excellent for real-time applications and prototype development. Renowned in 2025 for exceptional developer experience simplicity, achieving enterprise-level scalability through deep integration with Google Cloud Platform (GCP) ecosystem. However, mid-scale to enterprise companies are increasingly considering alternatives due to vendor lock-in concerns.
Details
Firebase Auth 2025 provides SDKs across wide platforms including iOS, Android, Web, Flutter, Unity, establishing itself as an industry-standard authentication platform. Supports email/password, phone number authentication, anonymous authentication, plus 20+ social login providers including Google, Facebook, Apple, GitHub, Microsoft, Twitter, Yahoo as standard. Also supports SAML (Web) and OpenID Connect custom providers, comprehensively providing multi-factor authentication (SMS-based 2FA), real-time session management, and user profile management in an integrated manner.
Key Features
- Comprehensive Platform Support: Complete SDKs for iOS, Android, Flutter, Web, C++, Unity
- Rich Social Login Options: 20+ OAuth providers and custom provider creation
- Scalable Free Tier: Completely free up to 50,000 MAU, SMS authentication pay-per-use
- Real-time Synchronization: Fast authentication state sync via Google Cloud infrastructure
- Multi-Factor Authentication: Comprehensive security with SMS-based 2FA and TOTP support
- GCP Ecosystem Integration: Seamless integration with Firebase Database, Cloud Functions, Analytics
Pros and Cons
Pros
- Overwhelming development speed enables authentication system implementation in minutes for mobile apps
- Free tier (50,000 MAU) allows building production-ready authentication systems with zero initial cost
- Google Cloud Platform's powerful infrastructure supports global scale applications
- Real-time database integration provides instant user information synchronization
- Rich SDKs and UI libraries provide platform-optimized experiences
- Firebase Console offers visual user management and analytics features
Cons
- Completely cloud-based making self-hosting impossible, vendor lock-in risk
- Unexpected costs as user numbers grow (especially SMS authentication: $0.01-$0.34/message)
- Limited custom authentication flow constraints, difficult to deviate from standard patterns
- Cannot be used in enterprises with strict data protection requirements or on-premise mandates
- Limited enterprise features (detailed RBAC, audit logs, custom SLA)
- Complex direct integration with other databases like PostgreSQL or MySQL
Reference Pages
Code Examples
Basic Setup
# Install Firebase SDK
npm install firebase
# Additional packages (for React)
npm install react-firebase-hooks
# Environment variable setup (optional)
npm install dotenv
Firebase Initial Configuration and Auth Initialization
// lib/firebase.ts
import { initializeApp, getApps } from 'firebase/app'
import { getAuth, connectAuthEmulator } from 'firebase/auth'
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}
// Initialize Firebase App (prevent duplicate initialization)
const app = getApps().length > 0 ? getApps()[0] : initializeApp(firebaseConfig)
// Initialize Firebase Auth
export const auth = getAuth(app)
// Emulator setup for development environment (optional)
if (process.env.NODE_ENV === 'development' && !auth.config.emulator) {
connectAuthEmulator(auth, 'http://localhost:9099')
}
export default app
Email/Password Authentication and User Registration
// hooks/useAuth.ts
import { useState } from 'react'
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
sendEmailVerification,
User
} from 'firebase/auth'
import { auth } from '@/lib/firebase'
export function useAuth() {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const signUp = async (email: string, password: string) => {
setLoading(true)
setError(null)
try {
const userCredential = await createUserWithEmailAndPassword(auth, email, password)
const user = userCredential.user
// Send email verification
await sendEmailVerification(user)
return {
user,
message: 'Confirmation email sent. Please check your email.'
}
} catch (error: any) {
setError(error.message)
throw error
} finally {
setLoading(false)
}
}
const signIn = async (email: string, password: string) => {
setLoading(true)
setError(null)
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password)
if (!userCredential.user.emailVerified) {
throw new Error('Email address not verified. Please check your confirmation email.')
}
return userCredential.user
} catch (error: any) {
setError(error.message)
throw error
} finally {
setLoading(false)
}
}
const logout = async () => {
try {
await signOut(auth)
} catch (error: any) {
setError(error.message)
throw error
}
}
return {
signUp,
signIn,
logout,
loading,
error
}
}
Social Login Implementation (Google, GitHub)
// components/SocialAuth.tsx
'use client'
import { useState } from 'react'
import {
signInWithPopup,
GoogleAuthProvider,
GithubAuthProvider,
UserCredential
} from 'firebase/auth'
import { auth } from '@/lib/firebase'
export function SocialAuth() {
const [loading, setLoading] = useState<string | null>(null)
const handleGoogleSignIn = async () => {
setLoading('google')
try {
const provider = new GoogleAuthProvider()
provider.addScope('profile')
provider.addScope('email')
const result = await signInWithPopup(auth, provider)
const credential = GoogleAuthProvider.credentialFromResult(result)
const token = credential?.accessToken
console.log('Google Access Token:', token)
console.log('User:', result.user)
return result.user
} catch (error: any) {
console.error('Google sign-in error:', error)
throw error
} finally {
setLoading(null)
}
}
const handleGitHubSignIn = async () => {
setLoading('github')
try {
const provider = new GithubAuthProvider()
provider.addScope('repo')
const result = await signInWithPopup(auth, provider)
const credential = GithubAuthProvider.credentialFromResult(result)
const token = credential?.accessToken
console.log('GitHub Access Token:', token)
console.log('User:', result.user)
return result.user
} catch (error: any) {
console.error('GitHub sign-in error:', error)
throw error
} finally {
setLoading(null)
}
}
return (
<div className="space-y-3">
<button
onClick={handleGoogleSignIn}
disabled={loading !== null}
className="w-full flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50"
>
{loading === 'google' ? (
'Signing in with Google...'
) : (
<>
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
Sign in with Google
</>
)}
</button>
<button
onClick={handleGitHubSignIn}
disabled={loading !== null}
className="w-full flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50"
>
{loading === 'github' ? (
'Signing in with GitHub...'
) : (
<>
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
Sign in with GitHub
</>
)}
</button>
</div>
)
}
Phone Number Authentication and reCAPTCHA Setup
// components/PhoneAuth.tsx
'use client'
import { useState, useEffect } from 'react'
import {
signInWithPhoneNumber,
RecaptchaVerifier,
ConfirmationResult,
PhoneAuthProvider,
signInWithCredential
} from 'firebase/auth'
import { auth } from '@/lib/firebase'
export function PhoneAuth() {
const [phoneNumber, setPhoneNumber] = useState('')
const [verificationCode, setVerificationCode] = useState('')
const [confirmationResult, setConfirmationResult] = useState<ConfirmationResult | null>(null)
const [loading, setLoading] = useState(false)
const [recaptchaVerifier, setRecaptchaVerifier] = useState<RecaptchaVerifier | null>(null)
useEffect(() => {
// Initialize reCAPTCHA Verifier
const verifier = new RecaptchaVerifier(auth, 'recaptcha-container', {
size: 'normal',
callback: () => {
console.log('reCAPTCHA authentication completed')
},
'expired-callback': () => {
console.log('reCAPTCHA expired')
}
})
setRecaptchaVerifier(verifier)
return () => {
verifier.clear()
}
}, [])
const sendVerificationCode = async () => {
if (!recaptchaVerifier) return
setLoading(true)
try {
const confirmation = await signInWithPhoneNumber(auth, phoneNumber, recaptchaVerifier)
setConfirmationResult(confirmation)
alert('Verification code sent. Please check your SMS.')
} catch (error: any) {
console.error('Phone authentication error:', error)
alert('Failed to send verification code: ' + error.message)
recaptchaVerifier.clear()
} finally {
setLoading(false)
}
}
const confirmVerificationCode = async () => {
if (!confirmationResult) return
setLoading(true)
try {
const userCredential = await confirmationResult.confirm(verificationCode)
console.log('Phone authentication successful:', userCredential.user)
alert('Authentication successful!')
} catch (error: any) {
console.error('Verification code confirmation error:', error)
alert('Verification code is incorrect: ' + error.message)
} finally {
setLoading(false)
}
}
return (
<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6 text-center">Phone Authentication</h2>
{!confirmationResult ? (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1">
Phone Number
</label>
<input
type="tel"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
placeholder="+1 555-123-4567"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<p className="text-sm text-gray-500 mt-1">
Enter in international format (e.g., +15551234567)
</p>
</div>
<div id="recaptcha-container" className="flex justify-center"></div>
<button
onClick={sendVerificationCode}
disabled={loading || !phoneNumber}
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Sending...' : 'Send Verification Code'}
</button>
</div>
) : (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1">
Verification Code (6 digits)
</label>
<input
type="text"
value={verificationCode}
onChange={(e) => setVerificationCode(e.target.value)}
placeholder="123456"
maxLength={6}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<button
onClick={confirmVerificationCode}
disabled={loading || verificationCode.length !== 6}
className="w-full bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700 disabled:opacity-50"
>
{loading ? 'Verifying...' : 'Complete Authentication'}
</button>
<button
onClick={() => {
setConfirmationResult(null)
setVerificationCode('')
}}
className="w-full text-gray-600 py-2 px-4 border border-gray-300 rounded-md hover:bg-gray-50"
>
Change Phone Number
</button>
</div>
)}
</div>
)
}
Authentication State Management and Protected Routes
// components/AuthProvider.tsx
'use client'
import { createContext, useContext, useEffect, useState } from 'react'
import { User, onAuthStateChanged } from 'firebase/auth'
import { auth } from '@/lib/firebase'
interface AuthContextType {
user: User | null
loading: boolean
}
const AuthContext = createContext<AuthContextType>({
user: null,
loading: true
})
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user)
setLoading(false)
})
return () => unsubscribe()
}, [])
return (
<AuthContext.Provider value={{ user, loading }}>
{children}
</AuthContext.Provider>
)
}
export const useAuthContext = () => useContext(AuthContext)
// components/ProtectedRoute.tsx
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
import { useAuthContext } from './AuthProvider'
interface ProtectedRouteProps {
children: React.ReactNode
redirectTo?: string
}
export function ProtectedRoute({
children,
redirectTo = '/login'
}: ProtectedRouteProps) {
const { user, loading } = useAuthContext()
const router = useRouter()
useEffect(() => {
if (!loading && !user) {
router.push(redirectTo)
}
}, [user, loading, router, redirectTo])
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div>
</div>
)
}
if (!user) {
return null
}
return <>{children}</>
}
User Profile Management and ID Token Retrieval
// components/UserProfile.tsx
'use client'
import { useState, useEffect } from 'react'
import {
updateProfile,
updateEmail,
updatePassword,
sendEmailVerification,
deleteUser,
User
} from 'firebase/auth'
import { useAuthContext } from './AuthProvider'
export function UserProfile() {
const { user } = useAuthContext()
const [displayName, setDisplayName] = useState('')
const [email, setEmail] = useState('')
const [newPassword, setNewPassword] = useState('')
const [loading, setLoading] = useState(false)
const [idToken, setIdToken] = useState<string | null>(null)
useEffect(() => {
if (user) {
setDisplayName(user.displayName || '')
setEmail(user.email || '')
// Get ID token
user.getIdToken().then(setIdToken)
}
}, [user])
const handleUpdateProfile = async () => {
if (!user) return
setLoading(true)
try {
await updateProfile(user, {
displayName: displayName
})
alert('Profile updated successfully!')
} catch (error: any) {
console.error('Profile update error:', error)
alert('Update failed: ' + error.message)
} finally {
setLoading(false)
}
}
const handleUpdateEmail = async () => {
if (!user) return
setLoading(true)
try {
await updateEmail(user, email)
await sendEmailVerification(user)
alert('Email address updated. Please check your confirmation email.')
} catch (error: any) {
console.error('Email update error:', error)
alert('Update failed: ' + error.message)
} finally {
setLoading(false)
}
}
const handleUpdatePassword = async () => {
if (!user || !newPassword) return
setLoading(true)
try {
await updatePassword(user, newPassword)
setNewPassword('')
alert('Password updated successfully!')
} catch (error: any) {
console.error('Password update error:', error)
alert('Update failed: ' + error.message)
} finally {
setLoading(false)
}
}
const handleDeleteAccount = async () => {
if (!user) return
if (!confirm('Are you sure you want to delete your account? This action cannot be undone.')) {
return
}
setLoading(true)
try {
await deleteUser(user)
alert('Account deleted successfully.')
} catch (error: any) {
console.error('Account deletion error:', error)
alert('Deletion failed: ' + error.message)
} finally {
setLoading(false)
}
}
if (!user) {
return <div>Authentication required</div>
}
return (
<div className="max-w-2xl mx-auto p-6 space-y-6">
<h1 className="text-2xl font-bold mb-6">Profile Settings</h1>
{/* User information display */}
<div className="bg-gray-50 p-4 rounded-lg">
<h3 className="font-semibold mb-2">User Information</h3>
<div className="space-y-2 text-sm">
<p><strong>UID:</strong> {user.uid}</p>
<p><strong>Created:</strong> {user.metadata.creationTime}</p>
<p><strong>Last Sign In:</strong> {user.metadata.lastSignInTime}</p>
<p><strong>Email Verified:</strong> {user.emailVerified ? 'Yes' : 'No'}</p>
<p><strong>Providers:</strong> {user.providerData.map(p => p.providerId).join(', ')}</p>
</div>
</div>
{/* Display name update */}
<div className="p-4 border rounded-lg">
<h3 className="font-semibold mb-2">Display Name</h3>
<div className="flex gap-2">
<input
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder="Display name"
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleUpdateProfile}
disabled={loading}
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
Update
</button>
</div>
</div>
{/* Email address update */}
<div className="p-4 border rounded-lg">
<h3 className="font-semibold mb-2">Email Address</h3>
<div className="flex gap-2">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email address"
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleUpdateEmail}
disabled={loading}
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
Update
</button>
</div>
</div>
{/* Password update */}
<div className="p-4 border rounded-lg">
<h3 className="font-semibold mb-2">Change Password</h3>
<div className="flex gap-2">
<input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
placeholder="New password"
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleUpdatePassword}
disabled={loading || !newPassword}
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
Change
</button>
</div>
</div>
{/* ID Token display */}
{idToken && (
<div className="p-4 bg-gray-50 rounded-lg">
<h3 className="font-semibold mb-2">ID Token</h3>
<textarea
value={idToken}
readOnly
rows={4}
className="w-full px-3 py-2 text-xs font-mono bg-white border border-gray-300 rounded-md"
/>
</div>
)}
{/* Dangerous operations */}
<div className="p-4 border border-red-200 bg-red-50 rounded-lg">
<h3 className="font-semibold text-red-800 mb-2">Dangerous Operations</h3>
<button
onClick={handleDeleteAccount}
disabled={loading}
className="bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700 disabled:opacity-50"
>
Delete Account
</button>
<p className="text-sm text-red-600 mt-2">
This action cannot be undone. Please proceed with caution.
</p>
</div>
</div>
)
}
Multi-Factor Authentication (MFA) Implementation
// components/MultiFactorAuth.tsx
'use client'
import { useState, useEffect } from 'react'
import {
multiFactor,
TotpMultiFactorGenerator,
TotpSecret,
PhoneAuthProvider,
PhoneMultiFactorGenerator,
RecaptchaVerifier
} from 'firebase/auth'
import { useAuthContext } from './AuthProvider'
import { auth } from '@/lib/firebase'
export function MultiFactorAuth() {
const { user } = useAuthContext()
const [totpSecret, setTotpSecret] = useState<TotpSecret | null>(null)
const [verificationCode, setVerificationCode] = useState('')
const [qrCodeUrl, setQrCodeUrl] = useState('')
const [enrolledFactors, setEnrolledFactors] = useState<any[]>([])
const [loading, setLoading] = useState(false)
useEffect(() => {
if (user) {
loadEnrolledFactors()
}
}, [user])
const loadEnrolledFactors = () => {
if (!user) return
const multiFactorUser = multiFactor(user)
setEnrolledFactors(multiFactorUser.enrolledFactors)
}
const enrollTOTP = async () => {
if (!user) return
setLoading(true)
try {
const multiFactorUser = multiFactor(user)
const session = await multiFactorUser.getSession()
// Generate TOTP secret
const totpSecret = await TotpMultiFactorGenerator.generateSecret(session)
setTotpSecret(totpSecret)
// Generate QR code URL
const qrCodeUrl = totpSecret.generateQrCodeUrl(
user.email || '[email protected]',
'My App'
)
setQrCodeUrl(qrCodeUrl)
} catch (error: any) {
console.error('TOTP enrollment error:', error)
alert('MFA setup error: ' + error.message)
} finally {
setLoading(false)
}
}
const verifyAndFinalizeTOTP = async () => {
if (!totpSecret || !verificationCode) return
setLoading(true)
try {
const multiFactorUser = multiFactor(user!)
// Create TOTP assertion
const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment(
totpSecret,
verificationCode
)
// Finalize MFA
await multiFactorUser.enroll(multiFactorAssertion, 'TOTP App')
alert('TOTP multi-factor authentication is now enabled!')
setTotpSecret(null)
setVerificationCode('')
setQrCodeUrl('')
loadEnrolledFactors()
} catch (error: any) {
console.error('TOTP verification error:', error)
alert('Verification code is incorrect: ' + error.message)
} finally {
setLoading(false)
}
}
const unenrollFactor = async (factorUid: string) => {
if (!user) return
if (!confirm('Are you sure you want to disable this multi-factor authentication?')) {
return
}
setLoading(true)
try {
const multiFactorUser = multiFactor(user)
await multiFactorUser.unenroll(factorUid)
alert('Multi-factor authentication disabled.')
loadEnrolledFactors()
} catch (error: any) {
console.error('Unenroll error:', error)
alert('Disable operation failed: ' + error.message)
} finally {
setLoading(false)
}
}
if (!user) {
return <div>Authentication required</div>
}
return (
<div className="max-w-2xl mx-auto p-6 space-y-6">
<h1 className="text-2xl font-bold mb-6">Multi-Factor Authentication (MFA)</h1>
{/* Current configuration status */}
<div className="p-4 bg-gray-50 rounded-lg">
<h3 className="font-semibold mb-2">Current Configuration</h3>
{enrolledFactors.length === 0 ? (
<p className="text-gray-600">No multi-factor authentication configured</p>
) : (
<div className="space-y-2">
{enrolledFactors.map((factor) => (
<div key={factor.uid} className="flex items-center justify-between p-2 bg-white rounded border">
<div>
<p className="font-medium">{factor.displayName || factor.factorId}</p>
<p className="text-sm text-gray-500">Enrolled: {factor.enrollmentTime}</p>
</div>
<button
onClick={() => unenrollFactor(factor.uid)}
disabled={loading}
className="text-red-600 hover:text-red-800 text-sm disabled:opacity-50"
>
Disable
</button>
</div>
))}
</div>
)}
</div>
{/* TOTP setup */}
{!totpSecret ? (
<div className="p-4 border rounded-lg">
<h3 className="font-semibold mb-2">Authenticator App 2FA</h3>
<p className="text-gray-600 mb-4">
Enhance security using authenticator apps like Google Authenticator, Microsoft Authenticator, etc.
</p>
<button
onClick={enrollTOTP}
disabled={loading}
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Setting up...' : 'Setup Authenticator App'}
</button>
</div>
) : (
<div className="p-4 border rounded-lg">
<h3 className="font-semibold mb-4">Authenticator App Setup</h3>
<div className="space-y-4">
<div>
<p className="text-sm text-gray-600 mb-2">
1. Scan the following QR code with your authenticator app:
</p>
{qrCodeUrl && (
<div className="text-center">
<img
src={`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(qrCodeUrl)}`}
alt="QR Code"
className="mx-auto border"
/>
</div>
)}
</div>
<div>
<p className="text-sm text-gray-600 mb-2">
2. Enter the 6-digit code displayed in your authenticator app:
</p>
<div className="flex gap-2">
<input
type="text"
value={verificationCode}
onChange={(e) => setVerificationCode(e.target.value)}
placeholder="123456"
maxLength={6}
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={verifyAndFinalizeTOTP}
disabled={loading || verificationCode.length !== 6}
className="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 disabled:opacity-50"
>
{loading ? 'Verifying...' : 'Verify'}
</button>
</div>
</div>
<button
onClick={() => {
setTotpSecret(null)
setVerificationCode('')
setQrCodeUrl('')
}}
className="text-gray-600 text-sm hover:text-gray-800"
>
Cancel
</button>
</div>
</div>
)}
</div>
)
}