Search code examples
iosswiftapple-loginasauthorizationcontroller

Providing Apple Login from a Swift Package


Our team is attempting to break up our monolithic app into Swift Package components.

One of the elements we are migrating is our Authentication Systems.

We're running into a particular issue with Apple, where the exact same code executed in the app works, but in the swift package it does not.

In both scenarios, the log in function fires and the user can choose to log in, but when that code lives in a Swift Package, the ASAuthorizationAppleIDCredential delegate callback never fires.

The login is still working, as the app changes the Apple Login UI to show an existing user sign in, I just never get the credential data.

The code in question is below, and it revolves around the ASAuthorizationController. If that object is instantiated in the App, even if it is passed as a parameter to the Swift Package the login will succeed and the delegate will fire. It's as though it's silently pulling some local environment element when it's created.

Is there a way I can pass a UIWindow or UIViewController to the AuthenticationServices pipeline (as GoogleSignIn allows) so that the logic can reside in a package?

// 1. Assign the Listeners that will handle the responses
signInWithAppleViewModel = SignInWithAppleViewModel()
// 2. Instantiate the AuthorizationAppleIDProvider
let provider = ASAuthorizationAppleIDProvider()
// 3. Create a request with the help of provider - ASAuthorizationAppleIDRequest
let request = provider.createRequest()
// 4. Scope to contact information to be requested from the user during authentication.
request.requestedScopes = [.fullName, .email]
// 5. A controller that manages authorization requests created by a provider.
let controller = ASAuthorizationController(authorizationRequests: [request])
// 6. Set delegate to perform action
controller.delegate = signInWithAppleViewModel
// 7. Initiate the authorization flows.
controller.performRequests()

Solution

  • My team wrote a package that includes this functionality, called EasyFirebase. This package allows single-line code for sign-in with Apple on iOS 11.0+ and macOS 10.15+.

    To do this, follow the steps using the article here. As usual, you'll need the "Sign in with Apple" capability in your Xcode project, an email relay key in your Apple Developer account, and the Swift package itself, with installation instructions here.

    If you don't want to use the package, you can check out some sample code to implement Sign in With Apple the way you specified. If you do, you'll need to implement ASAuthorizationControllerPresentationContextProviding and override presentationAnchor(for:) in your code:

    public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
      ASPresentationAnchor()
    }
    
    public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
      if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
        guard let nonce = currentNonce else { ... }
        guard let appleIDToken = appleIDCredential.identityToken else { ... }
        guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else { ... }
        let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
        // ...
      }
    }
    
    public func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
      // An error occured...
    }