Firebase Authentication (Swift/iOS)
Library
Firebase Authentication (Swift/iOS)
Overview
Firebase Authentication for iOS (Swift) is a comprehensive authentication SDK for iOS applications provided by Google's Firebase platform. As of 2025, it supports complete integration via Swift Package Manager and provides unified authentication experiences for iOS, macOS, tvOS, and watchOS applications. Supporting a wide range of authentication methods including email & password, OAuth (Google, Apple, Facebook, etc.), phone number authentication, and anonymous authentication, it works with both SwiftUI and UIKit. Complying with Apple's latest security guidelines, it provides enterprise-level user management features in conjunction with Firebase Console.
Details
Firebase Authentication for iOS (Swift) is an authentication solution optimized for the Apple ecosystem. Firebase Auth iOS SDK v10.x series is fully integrated with Swift Concurrency, enabling natural authentication implementation for iOS developers. The Auth class serves as the central entry point for authentication APIs, managing authentication state per FirebaseApp instance. With modular design, only necessary features can be selected and integrated, enabling bundle size optimization. Integration with Keychain Services automatically persists user credentials across app launches, providing seamless user experience. AuthDataResult and User objects provide structured access to user information and metadata.
Key Features
- Swift Concurrency Integration: Modern asynchronous processing with async/await and actor isolation
- Comprehensive Provider Support: 10+ authentication providers (Google, Apple, Facebook, etc.)
- Automatic Token Management: Automatic ID token refresh and keychain persistence
- Multi-Factor Authentication: Advanced security with SMS and TOTP
- App Check Integration: App instance authenticity verification
- Combine Support: Reactive programming with FirebaseCombineSwift
Advantages and Disadvantages
Advantages
- Complete support for Swift Concurrency and async/await enables modern, readable code implementation
- Seamless integration with Firebase ecosystem including Firestore, Cloud Functions, etc.
- Automatic token refresh and keychain management significantly reduces developer implementation burden
- Comprehensive OAuth provider support offers convenient diverse authentication options for users
- High development efficiency through Google's extensive documentation and community support
- Enterprise-level security features (multi-factor authentication, App Check) provided as standard
Disadvantages
- Dependency on Google Firebase ecosystem creates vendor lock-in risk
- Network connectivity required, with limitations on offline authentication features
- Large SDK size due to comprehensive features affects app bundle size
- Constraints exist for authentication flows requiring advanced customization
- Complex initial setup requiring both Firebase project and iOS app configuration
- Additional SDK integration required for some authentication providers (Facebook SDK, etc.)
Reference Pages
- Firebase Authentication iOS Documentation
- Firebase iOS SDK GitHub
- Firebase Authentication Swift API Reference
Usage Examples
Basic Setup and Firebase Configuration
// AppDelegate.swift - Firebase initialization
import UIKit
import Firebase
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Initialize Firebase using configuration file (GoogleService-Info.plist)
FirebaseApp.configure()
return true
}
}
// ContentView.swift - Basic authentication management
import SwiftUI
import Firebase
import FirebaseAuth
@MainActor
class AuthenticationManager: ObservableObject {
@Published var user: User?
@Published var isSignedIn = false
private var authStateHandle: AuthStateDidChangeListenerHandle?
init() {
// Start monitoring authentication state
setupAuthStateListener()
}
deinit {
// Remove authentication state listener
if let handle = authStateHandle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
private func setupAuthStateListener() {
authStateHandle = Auth.auth().addStateDidChangeListener { [weak self] _, user in
Task { @MainActor in
self?.user = user
self?.isSignedIn = user != nil
}
}
}
// Get current user information
func getCurrentUser() -> User? {
return Auth.auth().currentUser
}
// Get user's ID token
func getIDToken(forcingRefresh: Bool = false) async throws -> String? {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
return try await user.getIDToken(forcingRefresh: forcingRefresh)
}
}
enum AuthenticationError: Error, LocalizedError {
case userNotSignedIn
case authenticationFailed
case invalidCredentials
var errorDescription: String? {
switch self {
case .userNotSignedIn:
return "User is not signed in"
case .authenticationFailed:
return "Authentication failed"
case .invalidCredentials:
return "Invalid credentials"
}
}
}
Email & Password Authentication Implementation
// EmailAuthService.swift - Email authentication service
import Foundation
import FirebaseAuth
class EmailAuthService: ObservableObject {
// User registration (async/await version)
func createUser(email: String, password: String) async throws -> AuthDataResult {
do {
let authResult = try await Auth.auth().createUser(withEmail: email, password: password)
// Send email verification immediately after registration
try await sendEmailVerification()
return authResult
} catch let error as NSError {
throw mapAuthError(error)
}
}
// User sign-in
func signIn(email: String, password: String) async throws -> AuthDataResult {
do {
let authResult = try await Auth.auth().signIn(withEmail: email, password: password)
// Check email verification
if let user = authResult.user, !user.isEmailVerified {
print("Warning: Email not verified")
}
return authResult
} catch let error as NSError {
throw mapAuthError(error)
}
}
// Send email verification
func sendEmailVerification() async throws {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
try await user.sendEmailVerification()
}
// Password reset
func sendPasswordReset(email: String) async throws {
try await Auth.auth().sendPasswordReset(withEmail: email)
}
// Update email address (requires reauthentication)
func updateEmail(newEmail: String, currentPassword: String) async throws {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
// Reauthentication required for security-sensitive operations
let credential = EmailAuthProvider.credential(withEmail: user.email ?? "", password: currentPassword)
try await user.reauthenticate(with: credential)
// Update email address
try await user.sendEmailVerification(beforeUpdatingEmail: newEmail)
}
// Update password
func updatePassword(currentPassword: String, newPassword: String) async throws {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
// Reauthentication
let credential = EmailAuthProvider.credential(withEmail: user.email ?? "", password: currentPassword)
try await user.reauthenticate(with: credential)
// Update password
try await user.updatePassword(to: newPassword)
}
// Sign out
func signOut() throws {
try Auth.auth().signOut()
}
// Error mapping
private func mapAuthError(_ error: NSError) -> Error {
guard let errorCode = AuthErrorCode(rawValue: error.code) else {
return AuthenticationError.authenticationFailed
}
switch errorCode {
case .emailAlreadyInUse:
return NSError(domain: "Auth", code: 1, userInfo: [NSLocalizedDescriptionKey: "This email address is already in use"])
case .weakPassword:
return NSError(domain: "Auth", code: 2, userInfo: [NSLocalizedDescriptionKey: "Password is too weak"])
case .invalidEmail:
return NSError(domain: "Auth", code: 3, userInfo: [NSLocalizedDescriptionKey: "Invalid email address"])
case .userNotFound:
return NSError(domain: "Auth", code: 4, userInfo: [NSLocalizedDescriptionKey: "User not found"])
case .wrongPassword:
return NSError(domain: "Auth", code: 5, userInfo: [NSLocalizedDescriptionKey: "Wrong password"])
default:
return AuthenticationError.authenticationFailed
}
}
}
// SwiftUI usage example
struct LoginView: View {
@StateObject private var authService = EmailAuthService()
@State private var email = ""
@State private var password = ""
@State private var isSignUp = false
@State private var errorMessage = ""
@State private var isLoading = false
var body: some View {
VStack(spacing: 20) {
TextField("Email Address", text: $email)
.textFieldStyle(RoundedBorderTextFieldStyle())
.autocapitalization(.none)
SecureField("Password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
if !errorMessage.isEmpty {
Text(errorMessage)
.foregroundColor(.red)
.font(.caption)
}
Button(action: performAuthentication) {
Text(isSignUp ? "Create Account" : "Sign In")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.disabled(isLoading)
Button("Forgot Password?") {
Task {
try? await authService.sendPasswordReset(email: email)
}
}
.font(.caption)
Toggle("Create New Account", isOn: $isSignUp)
}
.padding()
}
private func performAuthentication() {
Task {
isLoading = true
errorMessage = ""
do {
if isSignUp {
_ = try await authService.createUser(email: email, password: password)
} else {
_ = try await authService.signIn(email: email, password: password)
}
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
}
Social Authentication (Google & Apple) Implementation
// SocialAuthService.swift - Social authentication service
import Foundation
import FirebaseAuth
import GoogleSignIn
import AuthenticationServices
class SocialAuthService: NSObject, ObservableObject {
// MARK: - Google Sign-In
func signInWithGoogle() async throws -> AuthDataResult {
// Check Google Sign-In configuration
guard let clientID = FirebaseApp.app()?.options.clientID else {
throw NSError(domain: "GoogleSignIn", code: -1, userInfo: [NSLocalizedDescriptionKey: "Google Client ID not found"])
}
// Google Sign-In configuration
GIDSignIn.sharedInstance.configuration = GIDConfiguration(clientID: clientID)
// Execute on main thread for UI access
guard let presentingViewController = await UIApplication.shared.windows.first?.rootViewController else {
throw NSError(domain: "GoogleSignIn", code: -2, userInfo: [NSLocalizedDescriptionKey: "Root view controller not found"])
}
// Execute Google Sign-In
let result = try await GIDSignIn.sharedInstance.signIn(withPresenting: presentingViewController)
let user = result.user
guard let idToken = user.idToken?.tokenString else {
throw NSError(domain: "GoogleSignIn", code: -3, userInfo: [NSLocalizedDescriptionKey: "ID token not found"])
}
// Create Firebase credential
let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: user.accessToken.tokenString)
// Execute Firebase authentication
return try await Auth.auth().signIn(with: credential)
}
// MARK: - Apple Sign-In
func signInWithApple() async throws -> AuthDataResult {
return try await withCheckedThrowingContinuation { continuation in
let request = ASAuthorizationAppleIDProvider().createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = AppleSignInDelegate(continuation: continuation)
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
}
// MARK: - Facebook Sign-In (Facebook SDK required)
func signInWithFacebook() async throws -> AuthDataResult {
// Facebook SDK implementation required
// Add pod 'FBSDKLoginKit' to Podfile
throw NSError(domain: "FacebookSignIn", code: -1, userInfo: [NSLocalizedDescriptionKey: "Facebook SDK integration required"])
}
// MARK: - Anonymous Authentication
func signInAnonymously() async throws -> AuthDataResult {
return try await Auth.auth().signInAnonymously()
}
// MARK: - Account Linking
func linkAccount(with credential: AuthCredential) async throws -> AuthDataResult {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
return try await user.link(with: credential)
}
// MARK: - Provider Removal
func unlink(provider: String) async throws -> User {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
return try await user.unlink(fromProvider: provider)
}
}
// Apple Sign-In Delegate
class AppleSignInDelegate: NSObject, ASAuthorizationControllerDelegate {
private let continuation: CheckedContinuation<AuthDataResult, Error>
init(continuation: CheckedContinuation<AuthDataResult, Error>) {
self.continuation = continuation
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
Task {
do {
let authResult = try await processAppleCredential(appleIDCredential)
continuation.resume(returning: authResult)
} catch {
continuation.resume(throwing: error)
}
}
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
continuation.resume(throwing: error)
}
private func processAppleCredential(_ credential: ASAuthorizationAppleIDCredential) async throws -> AuthDataResult {
guard let nonce = generateNonce() else {
throw NSError(domain: "AppleSignIn", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unable to generate nonce"])
}
guard let appleIDToken = credential.identityToken,
let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
throw NSError(domain: "AppleSignIn", code: -2, userInfo: [NSLocalizedDescriptionKey: "Unable to fetch identity token"])
}
let firebaseCredential = OAuthProvider.credential(withProviderID: "apple.com",
idToken: idTokenString,
rawNonce: nonce)
return try await Auth.auth().signIn(with: firebaseCredential)
}
private func generateNonce() -> String? {
// Random nonce generation (implementation required)
return UUID().uuidString
}
}
// Presentation Context Provider
extension SocialAuthService: ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return UIApplication.shared.windows.first { $0.isKeyWindow } ?? UIWindow()
}
}
Phone Number Authentication and Multi-Factor Authentication
// PhoneAuthService.swift - Phone number authentication service
import Foundation
import FirebaseAuth
class PhoneAuthService: ObservableObject {
@Published var verificationID: String?
@Published var isCodeSent = false
// Send phone number verification code
func sendVerificationCode(phoneNumber: String) async throws {
// reCAPTCHA authentication (required in production)
let verificationID = try await PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil)
await MainActor.run {
self.verificationID = verificationID
self.isCodeSent = true
}
}
// Verify code and sign in
func verifyAndSignIn(verificationCode: String) async throws -> AuthDataResult {
guard let verificationID = verificationID else {
throw NSError(domain: "PhoneAuth", code: -1, userInfo: [NSLocalizedDescriptionKey: "Verification ID not found"])
}
let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID, verificationCode: verificationCode)
return try await Auth.auth().signIn(with: credential)
}
// Link phone number to existing account
func linkPhoneNumber(verificationCode: String) async throws -> AuthDataResult {
guard let user = Auth.auth().currentUser,
let verificationID = verificationID else {
throw AuthenticationError.userNotSignedIn
}
let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID, verificationCode: verificationCode)
return try await user.link(with: credential)
}
}
// MultiFactorAuthService.swift - Multi-factor authentication service
class MultiFactorAuthService: ObservableObject {
// Enroll multi-factor authentication
func enrollMultiFactor(phoneNumber: String) async throws {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
// Start multi-factor authentication session
let session = try await user.multiFactor.session()
// Add second factor via phone number
let verificationID = try await PhoneAuthProvider.provider().verifyPhoneNumber(withMultiFactorInfo: nil, multiFactorSession: session)
// Verification code input needs separate implementation
// self.verificationID = verificationID
}
// Complete MFA enrollment
func finalizeMFAEnrollment(verificationCode: String, verificationID: String, displayName: String?) async throws {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID, verificationCode: verificationCode)
let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
try await user.multiFactor.enroll(with: assertion, displayName: displayName)
}
// Resolve MFA sign-in
func resolveMFASignIn(resolver: MultiFactorResolver, verificationCode: String, verificationID: String) async throws -> AuthDataResult {
let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID, verificationCode: verificationCode)
let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
return try await resolver.resolveSignIn(with: assertion)
}
// Unenroll multi-factor authentication
func unenrollMultiFactor(factorInfo: MultiFactorInfo) async throws {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
try await user.multiFactor.unenroll(with: factorInfo)
}
}
Combine Framework Integration and Reactive Authentication
// ReactiveAuthService.swift - Combine integrated authentication service
import Foundation
import Combine
import FirebaseAuth
import FirebaseCombineSwift
class ReactiveAuthService: ObservableObject {
@Published var authState: AuthState = .unauthenticated
@Published var currentUser: User?
private var cancellables = Set<AnyCancellable>()
enum AuthState {
case loading
case authenticated(User)
case unauthenticated
case error(Error)
}
init() {
// Monitor Firebase Auth state changes with Combine
Auth.auth().authStateDidChangePublisher()
.map { user in
if let user = user {
return AuthState.authenticated(user)
} else {
return AuthState.unauthenticated
}
}
.receive(on: DispatchQueue.main)
.assign(to: \.authState, on: self)
.store(in: &cancellables)
// Monitor user information
Auth.auth().authStateDidChangePublisher()
.receive(on: DispatchQueue.main)
.assign(to: \.currentUser, on: self)
.store(in: &cancellables)
}
// Anonymous sign-in Combine version
func signInAnonymously() -> AnyPublisher<AuthDataResult, Error> {
Auth.auth().signInAnonymously()
}
// Email & password sign-in Combine version
func signIn(email: String, password: String) -> AnyPublisher<AuthDataResult, Error> {
Auth.auth().signIn(withEmail: email, password: password)
}
// User creation Combine version
func createUser(email: String, password: String) -> AnyPublisher<AuthDataResult, Error> {
Auth.auth().createUser(withEmail: email, password: password)
}
// User information update Combine chain
func updateUserProfile(displayName: String?, photoURL: URL?) -> AnyPublisher<Void, Error> {
guard let user = Auth.auth().currentUser else {
return Fail(error: AuthenticationError.userNotSignedIn)
.eraseToAnyPublisher()
}
let changeRequest = user.createProfileChangeRequest()
changeRequest.displayName = displayName
changeRequest.photoURL = photoURL
return changeRequest.commitChanges()
}
// ID token retrieval with automatic refresh
func getIDTokenPublisher(forcingRefresh: Bool = false) -> AnyPublisher<String, Error> {
guard let user = Auth.auth().currentUser else {
return Fail(error: AuthenticationError.userNotSignedIn)
.eraseToAnyPublisher()
}
return user.getIDToken(forcingRefresh: forcingRefresh)
}
// Authentication flow chain example
func completeRegistrationFlow(email: String, password: String, displayName: String) -> AnyPublisher<User, Error> {
createUser(email: email, password: password)
.flatMap { authResult -> AnyPublisher<Void, Error> in
// Update profile
return self.updateUserProfile(displayName: displayName, photoURL: nil)
}
.flatMap { _ -> AnyPublisher<Void, Error> in
// Send email verification
guard let user = Auth.auth().currentUser else {
return Fail(error: AuthenticationError.userNotSignedIn)
.eraseToAnyPublisher()
}
return user.sendEmailVerification()
}
.map { _ in
// Finally return user object
Auth.auth().currentUser!
}
.eraseToAnyPublisher()
}
// Error handling and retry
func signInWithRetry(email: String, password: String, maxRetries: Int = 3) -> AnyPublisher<AuthDataResult, Error> {
signIn(email: email, password: password)
.retry(maxRetries)
.catch { error -> AnyPublisher<AuthDataResult, Error> in
// Fallback to anonymous login for specific errors
if let authError = error as NSError?, authError.code == AuthErrorCode.userNotFound.rawValue {
return self.signInAnonymously()
}
return Fail(error: error).eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}
// SwiftUI + Combine usage example
struct CombineAuthView: View {
@StateObject private var authService = ReactiveAuthService()
@State private var email = ""
@State private var password = ""
@State private var displayName = ""
private var cancellables = Set<AnyCancellable>()
var body: some View {
VStack {
switch authService.authState {
case .loading:
ProgressView("Authenticating...")
case .authenticated(let user):
Text("Welcome, \(user.displayName ?? "User")")
Button("Sign Out") {
try? Auth.auth().signOut()
}
case .unauthenticated:
loginForm
case .error(let error):
Text("Error: \(error.localizedDescription)")
.foregroundColor(.red)
}
}
}
private var loginForm: some View {
VStack {
TextField("Email Address", text: $email)
SecureField("Password", text: $password)
TextField("Display Name", text: $displayName)
Button("Register") {
authService.completeRegistrationFlow(email: email, password: password, displayName: displayName)
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { completion in
if case .failure(let error) = completion {
print("Registration error: \(error)")
}
},
receiveValue: { user in
print("Registration successful: \(user.uid)")
}
)
.store(in: &cancellables)
}
}
}
}
Security Features and Token Management
// SecurityManager.swift - Security management
import Foundation
import FirebaseAuth
import FirebaseAppCheck
class SecurityManager: ObservableObject {
init() {
// App Check configuration (production environment)
configureAppCheck()
}
private func configureAppCheck() {
#if DEBUG
// Use debug provider in development environment
let providerFactory = AppCheckDebugProviderFactory()
AppCheck.setAppCheckProviderFactory(providerFactory)
#else
// Use App Attest (iOS 14.0+) or Device Check in production
if #available(iOS 14.0, *) {
let providerFactory = AppAttestProviderFactory()
AppCheck.setAppCheckProviderFactory(providerFactory)
} else {
let providerFactory = DeviceCheckProviderFactory()
AppCheck.setAppCheckProviderFactory(providerFactory)
}
#endif
}
// Secure ID token retrieval
func getSecureIDToken() async throws -> String {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
// Get latest token with forced refresh
let idToken = try await user.getIDToken(forcingRefresh: true)
// Basic token validation
try validateTokenStructure(idToken)
return idToken
}
private func validateTokenStructure(_ token: String) throws {
let parts = token.components(separatedBy: ".")
guard parts.count == 3 else {
throw NSError(domain: "TokenValidation", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid JWT structure"])
}
}
// Check user account security status
func checkAccountSecurity() async throws -> SecurityStatus {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
// Get latest user information
try await user.reload()
var issues: [SecurityIssue] = []
// Check email verification
if !user.isEmailVerified {
issues.append(.emailNotVerified)
}
// Check multi-factor authentication
if user.multiFactor.enrolledFactors.isEmpty {
issues.append(.noMultiFactorAuth)
}
// Check password strength (based on last sign-in time)
if let metadata = user.metadata {
let daysSinceLastSignIn = Date().timeIntervalSince(metadata.lastSignInDate ?? Date()) / (24 * 60 * 60)
if daysSinceLastSignIn > 90 {
issues.append(.longTimeSinceLastSignIn)
}
}
return SecurityStatus(issues: issues)
}
// Emergency account protection
func emergencyAccountProtection() async throws {
guard let user = Auth.auth().currentUser else {
throw AuthenticationError.userNotSignedIn
}
// Sign out from all devices (token invalidation)
try await user.getIDToken(forcingRefresh: true)
// Send password reset email
if let email = user.email {
try await Auth.auth().sendPasswordReset(withEmail: email)
}
// End current session
try Auth.auth().signOut()
}
// Get custom claims
func getCustomClaims() async throws -> [String: Any] {
let idTokenResult = try await Auth.auth().currentUser?.getIDTokenResult(forcingRefresh: true)
return idTokenResult?.claims ?? [:]
}
}
struct SecurityStatus {
let issues: [SecurityIssue]
var isSecure: Bool {
return issues.isEmpty
}
var riskLevel: RiskLevel {
switch issues.count {
case 0:
return .low
case 1...2:
return .medium
default:
return .high
}
}
}
enum SecurityIssue: CaseIterable {
case emailNotVerified
case noMultiFactorAuth
case longTimeSinceLastSignIn
case suspiciousActivity
var description: String {
switch self {
case .emailNotVerified:
return "Email address is not verified"
case .noMultiFactorAuth:
return "Multi-factor authentication is not set up"
case .longTimeSinceLastSignIn:
return "Has not signed in for a long time"
case .suspiciousActivity:
return "Suspicious activity detected"
}
}
}
enum RiskLevel {
case low, medium, high
var color: UIColor {
switch self {
case .low:
return .systemGreen
case .medium:
return .systemOrange
case .high:
return .systemRed
}
}
}