Skip to content

Commit 872fe7c

Browse files
phone auth
1 parent bee2add commit 872fe7c

6 files changed

Lines changed: 185 additions & 4 deletions

File tree

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ public protocol FacebookProviderProtocol {
1010
@MainActor func signInWithFacebook(isLimitedLogin: Bool) async throws -> AuthCredential
1111
}
1212

13+
public protocol PhoneAuthProviderProtocol {
14+
@MainActor func verifyPhoneNumber(phoneNumber: String) async throws -> String
15+
}
16+
1317
public enum AuthenticationProvider {
1418
case email
1519
case google
@@ -70,11 +74,13 @@ final class AuthListenerManager {
7074
public final class AuthService {
7175
public init(configuration: AuthConfiguration = AuthConfiguration(), auth: Auth = Auth.auth(),
7276
googleProvider: GoogleProviderProtocol? = nil,
73-
facebookProvider: FacebookProviderProtocol? = nil) {
77+
facebookProvider: FacebookProviderProtocol? = nil,
78+
phoneAuthProvider: PhoneAuthProviderProtocol? = nil) {
7479
self.auth = auth
7580
self.configuration = configuration
7681
self.googleProvider = googleProvider
7782
self.facebookProvider = facebookProvider
83+
self.phoneAuthProvider = phoneAuthProvider
7884
string = StringUtils(bundle: configuration.customStringsBundle ?? Bundle.module)
7985
listenerManager = AuthListenerManager(auth: auth, authEnvironment: self)
8086
}
@@ -91,6 +97,7 @@ public final class AuthService {
9197
private var listenerManager: AuthListenerManager?
9298
private let googleProvider: GoogleProviderProtocol?
9399
private let facebookProvider: FacebookProviderProtocol?
100+
private let phoneAuthProvider: PhoneAuthProviderProtocol?
94101

95102
private var safeGoogleProvider: GoogleProviderProtocol {
96103
get throws {
@@ -112,6 +119,16 @@ public final class AuthService {
112119
}
113120
}
114121

122+
private var safePhoneAuthProvider: PhoneAuthProviderProtocol {
123+
get throws {
124+
guard let provider = phoneAuthProvider else {
125+
throw AuthServiceError
126+
.notConfiguredProvider("`PhoneAuthProviderSwift` has not been configured")
127+
}
128+
return provider
129+
}
130+
}
131+
115132
private func safeActionCodeSettings(emailLinkSignIn: Bool = true) throws -> ActionCodeSettings {
116133
guard let actionCodeSettings = emailLinkSignIn ? configuration
117134
.emailLinkSignInActionCodeSettings : configuration.verifyEmailActionCodeSettings else {
@@ -253,3 +270,24 @@ public extension AuthService {
253270
}
254271
}
255272
}
273+
274+
// MARK: - Phone Auth Sign In
275+
276+
public extension AuthService {
277+
func verifyPhoneNumber(phoneNumber: String) async throws -> String {
278+
return try await safePhoneAuthProvider.verifyPhoneNumber(phoneNumber: phoneNumber)
279+
}
280+
281+
func signInWithPhoneNumber(verificationID: String, verificationCode: String) async throws {
282+
authenticationState = .authenticating
283+
do {
284+
let credential = PhoneAuthProvider.provider()
285+
.credential(withVerificationID: verificationID, verificationCode: verificationCode)
286+
try await signIn(with: credential)
287+
updateAuthenticationState()
288+
} catch {
289+
authenticationState = .unauthenticated
290+
throw error
291+
}
292+
}
293+
}

FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/FirebasePhoneAuthSwiftUI.swift

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
@preconcurrency import FirebaseAuth
2+
import FirebaseAuthSwiftUI
3+
4+
public typealias VerificationID = String
5+
6+
public class PhoneAuthProviderSwift: @preconcurrency PhoneAuthProviderProtocol {
7+
public init() {}
8+
9+
@MainActor public func verifyPhoneNumber(phoneNumber: String) async throws -> VerificationID {
10+
return try await withCheckedThrowingContinuation { continuation in
11+
PhoneAuthProvider.provider()
12+
.verifyPhoneNumber(phoneNumber, uiDelegate: nil) { verificationID, error in
13+
if let error = error {
14+
continuation.resume(throwing: error)
15+
return
16+
}
17+
continuation.resume(returning: verificationID!)
18+
}
19+
}
20+
}
21+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Foundation
2+
3+
class PhoneUtils {
4+
static func isValidPhoneNumber(_ phoneNumber: String) -> Bool {
5+
guard phoneNumber.first == "+" else {
6+
return false
7+
}
8+
9+
let digits = phoneNumber.dropFirst()
10+
guard !digits.isEmpty else {
11+
return false
12+
}
13+
14+
guard digits.allSatisfy({ $0.isNumber }) else {
15+
return false
16+
}
17+
18+
let minLength = 7
19+
let maxLength = 15
20+
guard digits.count >= minLength && digits.count <= maxLength else {
21+
return false
22+
}
23+
24+
return true
25+
}
26+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import FirebaseAuthSwiftUI
2+
import SwiftUI
3+
4+
@MainActor
5+
public struct PhoneAuthButtonView {
6+
@Environment(AuthService.self) private var authService
7+
@State private var errorMessage = ""
8+
@State private var phoneNumber = ""
9+
@State private var showVerificationCodeInput = false
10+
@State private var verificationCode = ""
11+
@State private var verificationID = ""
12+
13+
public init() {}
14+
}
15+
16+
extension PhoneAuthButtonView: View {
17+
public var body: some View {
18+
if authService.authenticationState != .authenticating {
19+
VStack {
20+
TextField("Enter phone number", text: $phoneNumber)
21+
.keyboardType(.phonePad)
22+
.padding()
23+
.background(Color(.systemGray6))
24+
.cornerRadius(8)
25+
.padding(.horizontal)
26+
27+
Button(action: {
28+
Task {
29+
do {
30+
let id = try await authService.verifyPhoneNumber(phoneNumber: phoneNumber)
31+
verificationID = id
32+
showVerificationCodeInput = true
33+
} catch {
34+
errorMessage = authService.string.localizedErrorMessage(
35+
for: error
36+
)
37+
}
38+
}
39+
}) {
40+
Text("Send Verification Code")
41+
.foregroundColor(.white)
42+
.padding()
43+
.frame(maxWidth: .infinity)
44+
.background(Color.blue)
45+
.cornerRadius(8)
46+
.padding(.horizontal)
47+
}.disabled(!PhoneUtils.isValidPhoneNumber(phoneNumber))
48+
}
49+
.sheet(isPresented: $showVerificationCodeInput) {
50+
VStack {
51+
TextField("Enter verification code", text: $verificationCode)
52+
.keyboardType(.numberPad)
53+
.padding()
54+
.background(Color(.systemGray6))
55+
.cornerRadius(8)
56+
.padding(.horizontal)
57+
58+
Button(action: {
59+
Task {
60+
do {
61+
try await authService.signInWithPhoneNumber(
62+
verificationID: verificationID,
63+
verificationCode: verificationCode
64+
)
65+
} catch {
66+
errorMessage = authService.string.localizedErrorMessage(for: error)
67+
}
68+
showVerificationCodeInput = false
69+
}
70+
}) {
71+
Text("Verify and Sign In")
72+
.foregroundColor(.white)
73+
.padding()
74+
.frame(maxWidth: .infinity)
75+
.background(Color.green)
76+
.cornerRadius(8)
77+
.padding(.horizontal)
78+
}
79+
}
80+
}
81+
} else {
82+
ProgressView()
83+
.progressViewStyle(CircularProgressViewStyle(tint: .white))
84+
.padding(.vertical, 8)
85+
.frame(maxWidth: .infinity)
86+
}
87+
Text(errorMessage).foregroundColor(.red)
88+
}
89+
}

samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/FirebaseSwiftUIExampleApp.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import FirebaseAuthSwiftUI
1111
import FirebaseCore
1212
import FirebaseFacebookSwiftUI
1313
import FirebaseGoogleSwiftUI
14+
import FirebasePhoneAuthSwiftUI
1415
import SwiftData
1516
import SwiftUI
1617

@@ -70,13 +71,21 @@ struct ContentView: View {
7071
actionCodeSettings.linkDomain = "flutterfire-e2e-tests.firebaseapp.com"
7172
actionCodeSettings.setIOSBundleID(Bundle.main.bundleIdentifier!)
7273
let configuration = AuthConfiguration(emailLinkSignInActionCodeSettings: actionCodeSettings)
73-
authService = AuthService(configuration: configuration, googleProvider: googleProvider)
74+
let facebookProvider = FacebookProviderSwift()
75+
let phoneAuthProvider = PhoneAuthProviderSwift()
76+
authService = AuthService(
77+
configuration: configuration,
78+
googleProvider: googleProvider,
79+
facebookProvider: facebookProvider,
80+
phoneAuthProvider: phoneAuthProvider
81+
)
7482
}
7583

7684
var body: some View {
7785
AuthPickerView {
7886
GoogleButtonView()
7987
FacebookButtonView()
88+
PhoneAuthButtonView()
8089
}.environment(authService)
8190
}
8291
}

0 commit comments

Comments
 (0)