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 Apple Developer:

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

  2. Se utilizzi Identity Platform per inviare email ai tuoi utenti, configura il tuo progetto con il servizio di inoltro email privato di Apple utilizzando l'indirizzo email che segue:

    noreply@project-id.firebaseapp.com
    

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

Conformità ai requisiti di Apple relativi ai dati anonimizzati

Apple offre agli utenti la possibilità di anonimizzare i propri dati, incluso il loro indirizzo email. Apple assegna agli utenti che selezionano questa opzione un'immagine offuscata indirizzo email con il dominio privaterelay.appleid.com.

La tua app deve rispettare eventuali norme o termini per gli sviluppatori di Apple relativi agli ID Apple anonimizzati. Ciò include l'ottenimento del consenso degli utenti prima associazione di informazioni che consentono l'identificazione personale (PII) a un indirizzo ID Apple. Le azioni che coinvolgono le PII includono, a titolo esemplificativo:

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

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

Configurare Apple come provider

Per configurare Apple come provider di identità:

  1. Vai alla pagina Provider di identità nella console Google Cloud.

    Vai alla pagina Provider di identità

  2. Fai clic su Add A Provider (Aggiungi un provider).

  3. Seleziona Mela dall'elenco.

  4. In Piattaforma, seleziona iOS.

  5. Inserisci l'ID pacchetto dell'app.

  6. Registra i domini della tua app facendo clic su Aggiungi dominio in Domini autorizzati. Ai fini dello sviluppo, localhost è già sono abilitate per impostazione predefinita.

  7. In Configura la tua applicazione, fai clic su iOS. Copia il nel codice dell'app per inizializzare Identity Platform SDK client.

  8. Fai clic su Salva.

Accesso degli utenti con l'SDK client

  1. Esegui l'accesso dell'utente e ottieni un token ID con il Framework dei servizi di autenticazione.

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

    Il nonce viene utilizzato per impedire attacchi di replay. Includi l'hash SHA-256 il tuo nonce 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 utilizzi creata nel passaggio precedente e una classe delegato per gestire risposta:

    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 proveniente dalla risposta di Apple con il nonce non sottoposto ad hashing per autenticarti 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 delle foto.

Se un utente sceglie di non condividere il proprio indirizzo email reale con l'app, Apple esegue il provisioning un indirizzo email univoco che l'utente possa condividere. L'email assume il formato xyz@privaterelay.appleid.com. Se hai configurato l'inoltro di email private servizio, Apple inoltra le email inviate all'indirizzo anonimo all'indirizzo .

Apple condivide solo 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 memorizza questi dati, che puoi recuperare utilizzando firebase.auth().currentUser.displayName durante le sessioni future. Tuttavia, se hai consentito agli utenti di accedere alla tua app utilizzando Apple prima dell'integrazione con Identity Platform, le informazioni degli utenti non è disponibile.

Eliminazione dell'account utente

Apple richiede app per iOS che supportano la creazione di account Deve inoltre consentire agli utenti di avviare l'eliminazione del proprio account dall'app.

Quando elimini un account utente, devi revocare il token dell'utente prima di eliminare l'account, nonché tutti i dati che hai archiviato per l'utente in Firestore, Cloud Storage e nel database Firebase Realtime. Per ulteriori informazioni, vedi Offrire l'eliminazione dell'account nell'app nella documentazione di supporto per gli sviluppatori di Apple.

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

Per revocare il token di un utente ed eliminare il suo account, esegui quanto segue:

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