I have a class where I want to handle the Sign in with Apple
logic. I have created an ASAuthorizationController
, set the presentationContextProvider
to be my view controller and set the delegate
to the class itself. When I perform the requests, the system dialog shows up. But upon canceling, my delegate method does not get called.
Here is my class:
final class AppleSignInProvider: NSObject {
typealias CompletionHandler = (Result<AuthCredential, Error>) -> Void
enum AuthError: LocalizedError {
case credential
case nonce
case identityToken
case identityTokenString
}
private let completion: CompletionHandler
private var authorizationController: ASAuthorizationController?
init(completion: @escaping CompletionHandler) {
self.completion = completion
}
private var currentNonce: String?
private func sha256(nonce: String) -> String {
let inputData = Data(nonce.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
return String(format: "%02x", $0)
}.joined()
return hashString
}
func signIn(viewController: UIViewController) {
let nonce = randomNonceString()
currentNonce = nonce
let request = ASAuthorizationAppleIDProvider().createRequest()
request.requestedScopes = [
.fullName, .email
]
request.nonce = sha256(nonce: nonce)
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.presentationContextProvider = viewController.view.window
authorizationController.delegate = self
authorizationController.performRequests()
self.authorizationController = authorizationController
}
// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
private func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
let charset: [Character] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
guard errorCode == errSecSuccess else {
fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
}
return random
}
randoms.forEach { random in
if remainingLength == 0 {
return
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
}
extension AppleSignInProvider: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
do {
guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential else {
throw AuthError.credential
}
guard let nonce = currentNonce else {
throw AuthError.nonce
}
guard let appleIDToken = appleIDCredential.identityToken else {
throw AuthError.identityToken
}
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
throw AuthError.identityTokenString
}
let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
completion(.success(credential))
} catch {
completion(.failure(error))
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
completion(.failure(error))
}
}
My delegate methods are called when the view controller is set as delegate, but that is not a fix for my issue.