Sign in with Apple authentication

Sofia Martinez Jan 2026
1 tab
import AuthenticationServices
import CryptoKit

class SignInWithAppleManager: NSObject, ObservableObject {
    @Published var isSignedIn = false
    @Published var userID: String?
    @Published var email: String?
    @Published var fullName: String?

    private var currentNonce: String?

    func signIn(presentingViewController: UIViewController) {
        let nonce = randomNonceString()
        currentNonce = nonce

        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.fullName, .email]
        request.nonce = sha256(nonce)

        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
        controller.presentationContextProvider = self
        controller.performRequests()
    }

    func checkCredentialState(userID: String) {
        let provider = ASAuthorizationAppleIDProvider()
        provider.getCredentialState(forUserID: userID) { state, error in
            DispatchQueue.main.async {
                switch state {
                case .authorized:
                    self.isSignedIn = true
                    self.userID = userID
                case .revoked, .notFound:
                    self.isSignedIn = false
                    self.userID = nil
                case .transferred:
                    // Handle app transfer
                    break
                @unknown default:
                    break
                }
            }
        }
    }

    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)
                if errorCode != errSecSuccess {
                    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
    }

    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
    }
}

// MARK: - ASAuthorizationControllerDelegate
extension SignInWithAppleManager: ASAuthorizationControllerDelegate {
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            guard let nonce = currentNonce else {
                print("Invalid state: A login callback was received, but no login request was sent.")
                return
            }

            guard let appleIDToken = appleIDCredential.identityToken,
                  let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
                print("Unable to fetch identity token")
                return
            }

            let userID = appleIDCredential.user
            let email = appleIDCredential.email
            let fullName = appleIDCredential.fullName

            DispatchQueue.main.async {
                self.userID = userID
                self.email = email
                self.isSignedIn = true

                if let fullName = fullName {
                    let firstName = fullName.givenName ?? ""
                    let lastName = fullName.familyName ?? ""
                    self.fullName = "\(firstName) \(lastName)".trimmingCharacters(in: .whitespaces)
                }

                // Save user ID for future credential checks
                UserDefaults.standard.set(userID, forKey: "appleUserID")
            }

            // Send to backend for verification
            sendToBackend(userID: userID, idToken: idTokenString, nonce: nonce)
        }
    }

    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        print("Sign in with Apple failed: \(error.localizedDescription)")
    }

    private func sendToBackend(userID: String, idToken: String, nonce: String) {
        // Send credentials to your backend for verification
        print("Sending to backend: \(userID)")
    }
}

// MARK: - ASAuthorizationControllerPresentationContextProviding
extension SignInWithAppleManager: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        guard let window = UIApplication.shared.connectedScenes
            .compactMap({ $0 as? UIWindowScene })
            .flatMap({ $0.windows })
            .first(where: { $0.isKeyWindow }) else {
            fatalError("No key window found")
        }
        return window
    }
}

// MARK: - SwiftUI Button
struct SignInWithAppleButton: View {
    @StateObject private var authManager = SignInWithAppleManager()

    var body: some View {
        Button(action: {
            if let viewController = UIApplication.shared.windows.first?.rootViewController {
                authManager.signIn(presentingViewController: viewController)
            }
        }) {
            SignInWithAppleButtonViewRepresentable()
                .frame(height: 50)
        }
    }
}

struct SignInWithAppleButtonViewRepresentable: UIViewRepresentable {
    func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
        return ASAuthorizationAppleIDButton(type: .signIn, style: .black)
    }

    func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) {}
}
1 file · swift Explain with highlit

Sign in with Apple provides secure, privacy-focused authentication required for apps with third-party login. Users create accounts with Face ID or Touch ID, and Apple generates unique identifiers per app. I use ASAuthorizationController to initiate the flow, requesting scopes like full name and email. The authorization delegate receives credentials with user identifier and optional identityToken for backend verification. For continuing users, I check credential state with ASAuthorizationAppleIDProvider. Email relay protects privacy—users can hide their email. Two-factor authentication is built-in. Backend validation verifies tokens with Apple's servers. Apps with third-party login must offer Sign in with Apple per App Store guidelines.