1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15+ import AuthenticationServices
16+ import CryptoKit
1517import FirebaseAuth
1618import FirebaseAuthSwiftUI
1719import FirebaseCore
1820import SwiftUI
1921
22+ // MARK: - Data Extensions
23+
24+ extension Data {
25+ var utf8String : String ? {
26+ return String ( data: self , encoding: . utf8)
27+ }
28+ }
29+
30+ extension ASAuthorizationAppleIDCredential {
31+ var authorizationCodeString : String ? {
32+ return authorizationCode? . utf8String
33+ }
34+
35+ var idTokenString : String ? {
36+ return identityToken? . utf8String
37+ }
38+ }
39+
40+ // MARK: - Authenticate With Apple Dialog
41+
42+ private func authenticateWithApple( ) async throws -> ( ASAuthorizationAppleIDCredential , String ) {
43+ return try await AuthenticateWithAppleDialog ( ) . authenticate ( )
44+ }
45+
46+ private class AuthenticateWithAppleDialog : NSObject {
47+ private var continuation : CheckedContinuation < ( ASAuthorizationAppleIDCredential , String ) , Error > ?
48+ private var currentNonce : String ?
49+
50+ func authenticate( ) async throws -> ( ASAuthorizationAppleIDCredential , String ) {
51+ return try await withCheckedThrowingContinuation { continuation in
52+ self . continuation = continuation
53+
54+ let appleIDProvider = ASAuthorizationAppleIDProvider ( )
55+ let request = appleIDProvider. createRequest ( )
56+ request. requestedScopes = [ . fullName, . email]
57+
58+ do {
59+ let nonce = try CryptoUtils . randomNonceString ( )
60+ currentNonce = nonce
61+ request. nonce = CryptoUtils . sha256 ( nonce)
62+ } catch {
63+ continuation. resume ( throwing: AuthServiceError . signInFailed ( underlying: error) )
64+ return
65+ }
66+
67+ let authorizationController = ASAuthorizationController ( authorizationRequests: [ request] )
68+ authorizationController. delegate = self
69+ authorizationController. performRequests ( )
70+ }
71+ }
72+ }
73+
74+ extension AuthenticateWithAppleDialog : ASAuthorizationControllerDelegate {
75+ func authorizationController(
76+ controller: ASAuthorizationController ,
77+ didCompleteWithAuthorization authorization: ASAuthorization
78+ ) {
79+ if let appleIDCredential = authorization. credential as? ASAuthorizationAppleIDCredential {
80+ if let nonce = currentNonce {
81+ continuation? . resume ( returning: ( appleIDCredential, nonce) )
82+ } else {
83+ continuation? . resume (
84+ throwing: AuthServiceError . signInFailed (
85+ underlying: NSError (
86+ domain: " AppleSignIn " ,
87+ code: - 1 ,
88+ userInfo: [ NSLocalizedDescriptionKey: " Missing nonce " ]
89+ )
90+ )
91+ )
92+ }
93+ } else {
94+ continuation? . resume (
95+ throwing: AuthServiceError . invalidCredentials ( " Missing Apple ID credential " )
96+ )
97+ }
98+ continuation = nil
99+ }
100+
101+ func authorizationController(
102+ controller: ASAuthorizationController ,
103+ didCompleteWithError error: Error
104+ ) {
105+ continuation? . resume ( throwing: AuthServiceError . signInFailed ( underlying: error) )
106+ continuation = nil
107+ }
108+ }
109+
110+ // MARK: - Apple Provider Swift
111+
20112public class AppleProviderSwift : AuthProviderSwift , DeleteUserSwift {
21113 public let scopes : [ String ]
22114 let providerId = " apple.com "
@@ -26,27 +118,22 @@ public class AppleProviderSwift: AuthProviderSwift, DeleteUserSwift {
26118 }
27119
28120 @MainActor public func createAuthCredential( ) async throws -> AuthCredential {
29- // TODO: Implement Apple Sign In credential creation
30- // This will need to use ASAuthorizationAppleIDProvider
31- let provider = OAuthProvider ( providerID: providerId)
32- return try await withCheckedThrowingContinuation { continuation in
33- provider. getCredentialWith ( nil ) { credential, error in
34- if let error {
35- continuation
36- . resume ( throwing: AuthServiceError . signInFailed ( underlying: error) )
37- } else if let credential {
38- continuation. resume ( returning: credential)
39- } else {
40- continuation
41- . resume ( throwing: AuthServiceError
42- . invalidCredentials ( " Apple did not provide a valid AuthCredential " ) )
43- }
44- }
121+ let ( appleIDCredential, nonce) = try await authenticateWithApple ( )
122+
123+ guard let idTokenString = appleIDCredential. idTokenString else {
124+ throw AuthServiceError . invalidCredentials ( " Unable to fetch identity token from Apple " )
45125 }
126+
127+ let credential = OAuthProvider . appleCredential (
128+ withIDToken: idTokenString,
129+ rawNonce: nonce,
130+ fullName: appleIDCredential. fullName
131+ )
132+
133+ return credential
46134 }
47135
48136 public func deleteUser( user: User ) async throws {
49- // TODO: Implement delete user functionality
50137 let operation = AppleDeleteUserOperation ( appleProvider: self )
51138 try await operation ( on: user)
52139 }
0 commit comments