Accesso degli utenti con Apple su iOS

Questo documento mostra come utilizzare Identity Platform per aggiungere Accedi con Apple alla tua app per iOS.

Prima di iniziare

Configurare l'app con Apple

Sul sito per sviluppatori Apple:

  1. Attiva la funzionalità Accedi con Apple per la tua app.

  2. Se utilizzi Identity Platform per inviare email agli utenti, configura il progetto con il servizio di inoltro email privato di Apple utilizzando il seguente indirizzo email:

    noreply@project-id.firebaseapp.com
    

    Puoi anche utilizzare un modello di email personalizzato, se la tua app ne ha uno.

Rispetto dei requisiti di dati anonimizzati di Apple

Apple offre agli utenti la possibilità di rendere anonimi i propri dati, compreso l'indirizzo email. Apple assegna agli utenti che selezionano questa opzione un indirizzo email offuscato con il dominio privaterelay.appleid.com.

La tua app deve essere conforme a tutti i termini o alle norme per gli sviluppatori applicabili relativi a Apple in merito agli ID Apple anonimizzati. Ciò include l'ottenimento del consenso dell'utente prima di associare eventuali informazioni che consentono l'identificazione personale (PII) a un ID Apple anonimo. Le azioni che riguardano PII includono, a titolo esemplificativo:

  • Collegamento di un indirizzo email a un ID Apple anonimo o viceversa.
  • Collegamento di un numero di telefono a un ID Apple anonimo o viceversa
  • Collegamento di una credenziale sociale anonima, come Facebook o Google, a un ID Apple anonimo o viceversa.

Per ulteriori informazioni, consulta il Contratto di licenza del programma per sviluppatori Apple per il tuo account sviluppatore Apple.

Configurazione di Apple come provider

Per configurare Apple come provider di identità:

  1. Vai alla pagina Provider di Identity Platform nella console Google Cloud.

    Vai alla pagina Provider di identità

  2. Fai clic su Aggiungi un fornitore.

  3. Seleziona Apple dall'elenco.

  4. In Piattaforma, seleziona iOS.

  5. Inserisci l'ID bolletta dell'app.

  6. Registra i domini della tua app facendo clic su Aggiungi dominio in Domini autorizzati. A scopo di sviluppo, localhost è già attivato per impostazione predefinita.

  7. In Configura la tua applicazione, fai clic su iOS. Copia lo snippet nel codice della tua app per inizializzare l'SDK client di Identity Platform.

  8. Fai clic su Salva.

Accesso degli utenti con l'SDK del client

  1. Eseguire l'accesso dell'utente e ottenere un token ID utilizzando il framework Servizi di autenticazione di Apple.

  2. Genera una stringa casuale, nota come nonce, chiamando SecRandomCopyBytes(_:_:_:).

    Il nonce viene utilizzato per impedire gli attacchi di replica. Includi l'hash SHA-256 del noce nella richiesta di autenticazione e Apple lo restituisce, non modificato, nella risposta. Identity Platform convalida quindi la risposta confrontando l'hash originale con il valore restituito da Apple.

  3. Avvia il flusso di accesso di Apple, incluso l'hash SHA-256 del nonce che hai creato nel passaggio precedente, e una classe delegata per gestire la risposta di Apple:

    Swift

    import CryptoKit
    
    // Unhashed nonce.
    fileprivate var currentNonce: String?
    
    @available(iOS 13, *)
    func startSignInWithAppleFlow() {
      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 = self
      authorizationController.performRequests()
    }
    
    @available(iOS 13, *)
    private func sha256(_ input: String) -> String {
      let inputData = Data(input.utf8)
      let hashedData = SHA256.hash(data: inputData)
      let hashString = hashedData.compactMap {
        return String(format: "%02x", $0)
      }.joined()
    
      return hashString
    }
    

    Objective-C

    @import CommonCrypto;
    
    - (void)startSignInWithAppleFlow {
      NSString *nonce = [self randomNonce:32];
      self.currentNonce = nonce;
      ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
      ASAuthorizationAppleIDRequest *request = [appleIDProvider createRequest];
      request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
      request.nonce = [self stringBySha256HashingString:nonce];
    
      ASAuthorizationController *authorizationController =
          [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
      authorizationController.delegate = self;
      authorizationController.presentationContextProvider = self;
      [authorizationController performRequests];
    }
    
    - (NSString *)stringBySha256HashingString:(NSString *)input {
      const char *string = [input UTF8String];
      unsigned char result[CC_SHA256_DIGEST_LENGTH];
      CC_SHA256(string, (CC_LONG)strlen(string), result);
    
      NSMutableString *hashed = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
      for (NSInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
        [hashed appendFormat:@"%02x", result[i]];
      }
      return hashed;
    }
    
  4. Gestisci la risposta di Apple nell'implementazione di ASAuthorizationControllerDelegate. Se l'accesso va a buon fine, utilizza il token ID della risposta di Apple con il nonce sottoposto ad hashing per l'autenticazione con Identity Platform:

    Swift

    @available(iOS 13.0, *)
    extension MainViewController: ASAuthorizationControllerDelegate {
    
      func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
          guard let nonce = currentNonce else {
            fatalError("Invalid state: A login callback was received, but no login request was sent.")
          }
          guard let appleIDToken = appleIDCredential.identityToken else {
            print("Unable to fetch identity token")
            return
          }
          guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
            print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
            return
          }
          // Initialize a Firebase credential.
          let credential = OAuthProvider.credential(withProviderID: "apple.com",
                                                    IDToken: idTokenString,
                                                    rawNonce: nonce)
          // Sign in with Firebase.
          Auth.auth().signIn(with: credential) { (authResult, error) in
            if error {
              // Error. If error.code == .MissingOrInvalidNonce, make sure
              // you're sending the SHA256-hashed nonce as a hex string with
              // your request to Apple.
              print(error.localizedDescription)
              return
            }
            // User is signed in to Firebase with Apple.
            // ...
          }
        }
      }
    
      func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // Handle error.
        print("Sign in with Apple errored: \(error)")
      }
    }
    

    Objective-C

    - (void)authorizationController:(ASAuthorizationController *)controller
      didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {
      if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
        ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
        NSString *rawNonce = self.currentNonce;
        NSAssert(rawNonce != nil, @"Invalid state: A login callback was received, but no login request was sent.");
    
        if (appleIDCredential.identityToken == nil) {
          NSLog(@"Unable to fetch identity token.");
          return;
        }
        NSString *idToken = [[NSString alloc] initWithData:appleIDCredential.identityToken
                                                  encoding:NSUTF8StringEncoding];
        if (idToken == nil) {
          NSLog(@"Unable to serialize id token from data: %@", appleIDCredential.identityToken);
        }
        // Initialize a Firebase credential.
        FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com"
                                                                            IDToken:idToken
                                                                           rawNonce:rawNonce];
        // Sign in with Firebase.
        [[FIRAuth auth] signInWithCredential:credential
                                  completion:^(FIRAuthDataResult * _Nullable authResult,
                                               NSError * _Nullable error) {
          if (error != nil) {
            // Error. If error.code == FIRAuthErrorCodeMissingOrInvalidNonce,
            // make sure you're sending the SHA256-hashed nonce as a hex string
            // with your request to Apple.
            return;
          }
          // Sign-in succeeded!
        }];
      }
    }
    
    - (void)authorizationController:(ASAuthorizationController *)controller
               didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) {
      NSLog(@"Sign in with Apple errored: %@", error);
    }
    

A differenza di molti altri provider di identità, Apple non fornisce l'URL di una foto.

Se un utente sceglie di non condividere la propria email con l'app, Apple eseguirà il provisioning di un indirizzo email univoco che potrà condividere con quell'utente. Questa email ha il seguente formato: xyz@privaterelay.appleid.com. Se hai configurato il servizio di inoltro email privato, Apple inoltra le email inviate all'indirizzo anonimo all'indirizzo email reale dell'utente.

Apple condivide le informazioni utente, ad esempio i nomi visualizzati, con le app la prima volta che un utente esegue l'accesso. Nella maggior parte dei casi, Identity Platform archivia questi dati, il che ti consente di recuperarli utilizzando firebase.auth().currentUser.displayName durante le sessioni future. Tuttavia, se hai consentito agli utenti di accedere all'app utilizzando Apple prima dell'integrazione con Identity Platform, le informazioni utente non sono disponibili.

Eliminazione di account utente

Apple richiede che le app per iOS che supportano la creazione degli account debbano consentire agli utenti di eliminare il proprio account dall'interno dell'app.

Quando elimini un account utente, devi revocare il token dell'utente prima di eliminare l'account dell'utente, nonché tutti i dati archiviati in Firestore, Cloud Storage e Firebase Realtime Database. Per ulteriori informazioni, consulta la sezione Offrire l'eliminazione di account nell'app nella documentazione di assistenza per gli sviluppatori di Apple.

Poiché Identity Platform non archivia i token utente quando gli utenti vengono creati con Accesso Apple, devi chiedere all'utente di accedere prima di revocare il token ed eliminare l'account. In alternativa, per evitare di chiedere all'utente di accedere di nuovo se un utente ha eseguito l'accesso con l'accesso Apple, puoi memorizzare il codice di autorizzazione da riutilizzare durante la revoca del token.

Per revocare il token di un utente ed eliminare il suo account, esegui questo comando:

Swift

let user = Auth.auth().currentUser

// Check if the user has a token.
if let providerData = user?.providerData {
  for provider in providerData {
    guard let provider = provider as? FIRUserInfo else {
      continue
    }
    if provider.providerID() == "apple.com" {
      isAppleProviderLinked = true
    }
  }
}

// Re-authenticate the user and revoke their token
if isAppleProviderLinked {
  let request = appleIDRequest(withState: "revokeAppleTokenAndDeleteUser")
  let controller = ASAuthorizationController(authorizationRequests: [request])
  controller.delegate = self
  controller.presentationContextProvider = self
  controller.performRequests()
} else {
  // Usual user deletion
}

func authorizationController(
  controller: ASAuthorizationController,
  didCompleteWithAuthorization authorization: ASAuthorization
) {
  if authorization.credential is ASAuthorizationAppleIDCredential {
    let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential
    if authorization.credential is ASAuthorizationAppleIDCredential {
      if appleIDCredential.state == "signIn" {
        // Sign in with Firebase.
        // ...
      } else if appleIDCredential.state == "revokeAppleTokenAndDeleteUser" {
        // Revoke token with Firebase.
        Auth.auth().revokeTokenWithAuthorizationCode(code) { error in
          if error != nil {
            // Token revocation failed.
          } else {
            // Token revocation succeeded then delete user again.
            let user = Auth.auth().currentUser
            user?.delete { error in
              // ...
            }
          }

        }
      }
    }
  }
}

Objective-C

FIRUser *user = [FIRAuth auth].currentUser;

// Check if the user has a token.
BOOL isAppleProviderLinked = false;
for (id<FIRUserInfo> provider in user.providerData) {
  if ([[provider providerID] isEqual:@"apple.com"]) {
    isAppleProviderLinked = true;
  }
}

// Re-authenticate the user and revoke their token
if (isAppleProviderLinked) {
  if (@available(iOS 13, *)) {
    ASAuthorizationAppleIDRequest *request =
        [self appleIDRequestWithState:@"revokeAppleTokenAndDeleteUser"];
    ASAuthorizationController *controller = [[ASAuthorizationController alloc]
        initWithAuthorizationRequests:@[ request ]];
    controller.delegate = self;
    controller.presentationContextProvider = self;
    [controller performRequests];
  }
} else {
  // Usual user deletion
}

- (void)authorizationController:(ASAuthorizationController *)controller
    didCompleteWithAuthorization:(ASAuthorization *)authorization
    API_AVAILABLE(ios(13.0)) {
  if ([authorization.credential
          isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
    ASAuthorizationAppleIDCredential *appleIDCredential =
        authorization.credential;

    if ([appleIDCredential.state isEqualToString:@"signIn"]) {
      // Sign in with Firebase.
      // ...
    } else if ([appleIDCredential.state
                  isEqualToString:@"revokeAppleTokenAndDeleteUser"]) {
      // Revoke token with Firebase.
      NSString *code =
          [[NSString alloc] initWithData:appleIDCredential.authorizationCode
                                encoding:NSUTF8StringEncoding];
      [[FIRAuth auth]
          revokeTokenWithAuthorizationCode:code
                                completion:^(NSError *_Nullable error) {
                                  if (error != nil) {
                                    // Token revocation failed.
                                  } else {
                                    // Token revocation succeeded then delete
                                    // user again.
                                    FIRUser *user = [FIRAuth auth].currentUser;
                                    [user deleteWithCompletion:^(
                                              NSError *_Nullable error){
                                        // ...
                                    }];
                                  }
                                }];
    }
  }
}

Passaggi successivi