iOS Auth & Firebase

Firebase Auth + SwiftUI: Integrazione Sign in with Apple e Gestione Custom User Claims

Per soddisfare le App Store Review Guidelines (Sezione 4.8), ogni app iOS che integra sistemi di login terzi deve offrire Sign in with Apple. Integrare Sign in with Apple con Firebase Auth consente di beneficiare della sicurezza nativa Apple combinata con la flessibilità del token JWT gestito da Firebase.

1. Configurazione in SwiftUI con AuthenticationServices

In SwiftUI possiamo utilizzare il componente nativo SignInWithAppleButton e scambiare il token di identità ID con Firebase Auth in maniera del tutto trasparente.

import SwiftUI
import AuthenticationServices
import FirebaseAuth
import CryptoKit

struct AppleSignInView: View {
    @State private var currentNonce: String?
    
    var body: some View {
        SignInWithAppleButton(.signIn) { request in
            let nonce = randomNonceString()
            currentNonce = nonce
            request.requestedScopes = [.fullName, .email]
            request.nonce = sha256(nonce)
        } onCompletion: { result in
            switch result {
            case .success(let authorization):
                guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential,
                      let appleIDToken = appleIDCredential.identityToken,
                      let idTokenString = String(data: appleIDToken, encoding: .utf8),
                      let nonce = currentNonce else { return }
                
                let credential = OAuthProvider.credential(withProviderID: "apple.com",
                                                          idToken: idTokenString,
                                                          rawNonce: nonce)
                
                Auth.auth().signIn(with: credential) { authResult, error in
                    if let error = error {
                        print("Errore login Firebase Auth: \(error.localizedDescription)")
                        return
                    }
                    print("Login completato con successo per UID: \(authResult?.user.uid ?? "")")
                }
            case .failure(let error):
                print("Errore Sign in with Apple: \(error.localizedDescription)")
            }
        }
        .frame(height: 50)
        .padding()
    }
    
    private func randomNonceString(length: Int = 32) -> String {
        var randomBytes = [UInt8](repeating: 0, count: length)
        _ = SecRandomCopyBytes(kSecRandomDefault, length, &randomBytes)
        let charset: [Character] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
        return String(randomBytes.map { charset[Int($0) % charset.count] })
    }

    private func sha256(_ input: String) -> String {
        let inputData = Data(input.utf8)
        let hashedData = SHA256.hash(data: inputData)
        return hashedData.compactMap { String(format: "%02x", $0) }.joined()
    }
}

2. Decodifica e Lettura dei Custom Claims in Swift

Una volta autenticato l'utente, i Custom Claims assegnati dal backend serverless (es. ruoli `admin`, `premium`) possono essere verificati nel client forzando l'aggiornamento del token ID:

func checkUserPermissions() {
    guard let user = Auth.auth().currentUser else { return }
    user.getIDTokenResult(forcingRefresh: true) { result, error in
        if let claims = result?.claims, let isPremium = claims["isPremium"] as? Bool, isPremium {
            print("Accesso Funzionalità Premium Accordato!")
        }
    }
}