I'm trying to implement authorization via Apple account, I've made several options from different sources, but none of the options worked. The problem is the following when I click on the authorization button I enter the password from the account and if the password is correct just nothing happens, control does not go to the method of processing the request didCompleteWithAuthorization, but if this window is closed, the method of request didCompleteWithError will work. Maybe someone has encountered this? I will be glad to be helped. Here is the code to familiarize you:
Button(action: {
Task{
do{
try await viewModel.singInApple()
//UserDefaults.standard.set(true, forKey: "status")
//NotificationCenter.default.post(name: NSNotification.Name("statusChange"), object: nil)
} catch{
print(error)
}
}
}, label: {
SignInWithAppleButtonViewRepresentable(type: .continue, style: .black).allowsHitTesting(false)
})
.frame(height: 45).onChange(of: viewModel.didSignInWithApple){
newValue in
if newValue{
UserDefaults.standard.set(true, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("statusChange"), object: nil)
}
}
@available(iOS 13.0, *)
extension AuthenticationViewModel: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
guard
let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential,
let appleIDToken = appleIDCredential.identityToken,
let idTokenString = String(data: appleIDToken, encoding: .utf8),
let nonce = currentNonce else{
print("error")
return
}
let tokens = SingInWithAppleResults(token: idTokenString, nonce: nonce, appleIDCredential: appleIDCredential)
Task{
do{
try await AuthenticationManager.shared.signInWithApple(tokens: tokens)
didSignInWithApple = true
} catch{
}
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// Handle error.
print("Sign in with Apple errored: \(error)")
}
}
extension UIViewController: ASAuthorizationControllerPresentationContextProviding{
public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.view.window!
}
}
@MainActor
final class AuthenticationViewModel: NSObject ,ObservableObject{
@Published var showAlert = false
@Published var didSignInWithApple: Bool = false
private var currentNonce: String?
func singInApple() async throws{
startSignInWithAppleFlow()
}
func startSignInWithAppleFlow() {
guard let topVC = Utilities.shared.topViewController() else{
return
}
let nonce = randomNonceString()
currentNonce = nonce
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
request.nonce = sha256(nonce)
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = topVC
authorizationController.performRequests()
}
private func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
var randomBytes = [UInt8](repeating: 0, count: length)
let errorCode = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes)
if errorCode != errSecSuccess {
fatalError(
"Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)"
)
}
let charset: [Character] =
Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
let nonce = randomBytes.map { byte in
// Pick a random character from the set, wrapping around if needed.
charset[Int(byte) % charset.count]
}
return String(nonce)
}
@available(iOS 13, *)
private func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
String(format: "%02x", $0)
}.joined()
return hashString
}
}
struct SingInWithAppleResults{
let token: String
let nonce: String
let appleIDCredential: ASAuthorizationAppleIDCredential
}
}
struct SignInWithAppleButtonViewRepresentable: UIViewRepresentable{
let type: ASAuthorizationAppleIDButton.ButtonType
let style: ASAuthorizationAppleIDButton.Style
func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
ASAuthorizationAppleIDButton(authorizationButtonType: type, authorizationButtonStyle: style)
}
func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) {
}
}
final class Utilities{
static let shared = Utilities()
private init(){}
@MainActor
func topViewController(controller: UIViewController? = nil) -> UIViewController? {
let controller = controller ?? UIApplication.shared.keyWindow?.rootViewController
if let navigationController = controller as? UINavigationController{
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController{
if let selected = tabController.selectedViewController{
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController{
return topViewController(controller: presented)
}
return controller
}
}
@discardableResult
func signInWithApple(tokens: SingInWithAppleResults) async throws -> AuthDataResultModel?{
let credential = OAuthProvider.appleCredential(withIDToken: tokens.token, rawNonce: tokens.nonce, fullName: tokens.appleIDCredential.fullName)
return try await signIn(credential: credential)
}
If I enter the wrong password, the password field is erased, if I enter the correct password nothing happens