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